video-stream-data 0.1.6__py3-none-any.whl
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.
- video_stream_data/__init__.py +177 -0
- video_stream_data/utils.py +20 -0
- video_stream_data-0.1.6.dist-info/METADATA +111 -0
- video_stream_data-0.1.6.dist-info/RECORD +7 -0
- video_stream_data-0.1.6.dist-info/WHEEL +5 -0
- video_stream_data-0.1.6.dist-info/licenses/LICENSE +28 -0
- video_stream_data-0.1.6.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Provide unified meta data definition for video streaming."""
|
|
2
|
+
from dataclasses import asdict, dataclass, field
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StreamControlType(str, Enum):
|
|
7
|
+
"""Different configuration types for the stream.
|
|
8
|
+
|
|
9
|
+
Taken from opencv2 device controls.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
INT = 'int'
|
|
13
|
+
BOOL = 'bool'
|
|
14
|
+
MENU = 'menu'
|
|
15
|
+
|
|
16
|
+
def __str__(self) -> str:
|
|
17
|
+
return self.value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Encoding(str, Enum):
|
|
21
|
+
"""Defines how the video stream gets encodied.
|
|
22
|
+
|
|
23
|
+
Should default to 'passthrough'.
|
|
24
|
+
Normally BGR in ROS, RGB in h264.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
PASSTHROUGH = 'passthrough'
|
|
28
|
+
BGR = 'bgr'
|
|
29
|
+
GREY = 'grey'
|
|
30
|
+
GREY16 = 'grey16'
|
|
31
|
+
RGB = 'rgb'
|
|
32
|
+
BGRA = 'bgra'
|
|
33
|
+
RGBA = 'rgba'
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return self.value
|
|
37
|
+
|
|
38
|
+
def to_ros_bridge(self) -> str:
|
|
39
|
+
"""Create string to use with the ROS2 CvBridge."""
|
|
40
|
+
return _ROS_ENCODINGS.get(self, self.value)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
_ROS_ENCODINGS = {
|
|
44
|
+
Encoding.BGR: 'bgr8',
|
|
45
|
+
Encoding.RGB: 'rgb8',
|
|
46
|
+
Encoding.BGRA: 'bgra8',
|
|
47
|
+
Encoding.RGBA: 'rgba8',
|
|
48
|
+
Encoding.GREY: 'mono8',
|
|
49
|
+
Encoding.GREY16: 'mono16',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Codec(str, Enum):
|
|
54
|
+
"""Codec type/Pixel format as fourcc."""
|
|
55
|
+
|
|
56
|
+
# see linuxpy/video/raw.py
|
|
57
|
+
|
|
58
|
+
MJPEG = 'MJPEG' # Motion-JPEG
|
|
59
|
+
YUYV = 'YUYV'
|
|
60
|
+
XVID = 'XVID' # MP4 files
|
|
61
|
+
H264 = 'H264'
|
|
62
|
+
GREY = 'GREY'
|
|
63
|
+
UNKNOWN = 'unknown'
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return self.value
|
|
67
|
+
|
|
68
|
+
def to_text(self) -> str:
|
|
69
|
+
"""Create human readable text."""
|
|
70
|
+
return _CODEC_TEXT.get(self, self.value)
|
|
71
|
+
|
|
72
|
+
def to_fourcc(self) -> str:
|
|
73
|
+
"""Create FourCC-Text for configuration of OpenCV2 VideoWriter."""
|
|
74
|
+
if self is Codec.MJPEG:
|
|
75
|
+
return 'MJPG'
|
|
76
|
+
return self.value
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
_CODEC_TEXT = {
|
|
80
|
+
Codec.MJPEG: 'Motion-JPEG',
|
|
81
|
+
Codec.YUYV: 'YUYV',
|
|
82
|
+
Codec.XVID: 'XVID/MP4',
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class StreamControl:
|
|
88
|
+
"""Provide a value the user can configure."""
|
|
89
|
+
|
|
90
|
+
type: StreamControlType # noqa: A003
|
|
91
|
+
name: str
|
|
92
|
+
writable: bool = False
|
|
93
|
+
min: float = 0 # noqa: A003
|
|
94
|
+
max: float = 0 # noqa: A003
|
|
95
|
+
step: int = 1
|
|
96
|
+
value: int | float | bool | None = None
|
|
97
|
+
# (key, label) pairs of a MENU control
|
|
98
|
+
options: list[tuple[str, str]] = field(default_factory=list)
|
|
99
|
+
|
|
100
|
+
def __post_init__(self):
|
|
101
|
+
self.type = StreamControlType(self.type)
|
|
102
|
+
# JSON and YAML have no tuples
|
|
103
|
+
self.options = [tuple(option) for option in self.options]
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass
|
|
107
|
+
class StreamValue:
|
|
108
|
+
"""Values requested by the user."""
|
|
109
|
+
|
|
110
|
+
# selected resolution [width, height]
|
|
111
|
+
resolution: list[int]
|
|
112
|
+
# current fps
|
|
113
|
+
fps: float
|
|
114
|
+
# selected control configurations
|
|
115
|
+
controls: dict[str, int | float | bool | None]
|
|
116
|
+
# selected encoding, e.g. bgr
|
|
117
|
+
encoding: Encoding = Encoding.PASSTHROUGH
|
|
118
|
+
# selected codec e.g. MJPEG for "Motion-JPEG" or YUYV for "YUYV 4:2:2"
|
|
119
|
+
codec: Codec = Codec.UNKNOWN
|
|
120
|
+
|
|
121
|
+
def __post_init__(self):
|
|
122
|
+
self.resolution = list(self.resolution)
|
|
123
|
+
self.encoding = Encoding(self.encoding)
|
|
124
|
+
self.codec = Codec(self.codec)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class StreamInfo:
|
|
129
|
+
"""Stream information provided by the device/host."""
|
|
130
|
+
|
|
131
|
+
name: str
|
|
132
|
+
# unique stream path
|
|
133
|
+
stream_path: str
|
|
134
|
+
# current configuration as own data structure so it can easily be handed
|
|
135
|
+
# over as dict from any service
|
|
136
|
+
values: StreamValue
|
|
137
|
+
# driver for example "uvcvideo" for a local OpenCV webcam or
|
|
138
|
+
# "webrtc" for a WebRTC Video Stream
|
|
139
|
+
driver: str = ''
|
|
140
|
+
# available codec
|
|
141
|
+
codec: list[Codec] = field(default_factory=list)
|
|
142
|
+
# all available controls from linuxpy
|
|
143
|
+
controls: list[StreamControl] = field(default_factory=list)
|
|
144
|
+
# all available resolutions [width, height], starting with the default
|
|
145
|
+
resolutions: list[list[int]] = field(default_factory=list)
|
|
146
|
+
# max. possible fps (at default resolution)
|
|
147
|
+
max_fps: float = 30.0
|
|
148
|
+
backend: str = 'v4l2' # (optional) OpenCV2 Backend (only if cv2 is used)
|
|
149
|
+
|
|
150
|
+
def __post_init__(self):
|
|
151
|
+
self.codec = [Codec(c) for c in self.codec]
|
|
152
|
+
if isinstance(self.values, dict):
|
|
153
|
+
self.values = StreamValue(**self.values)
|
|
154
|
+
self.controls = [
|
|
155
|
+
StreamControl(**c) if isinstance(c, dict) else c
|
|
156
|
+
for c in self.controls
|
|
157
|
+
]
|
|
158
|
+
self.resolutions = [list(r) for r in self.resolutions]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _dict_factory(data):
|
|
162
|
+
def convert_value(obj):
|
|
163
|
+
if isinstance(obj, Enum):
|
|
164
|
+
return obj.value
|
|
165
|
+
elif isinstance(obj, (list, tuple)):
|
|
166
|
+
return [convert_value(x) for x in obj]
|
|
167
|
+
elif isinstance(obj, set):
|
|
168
|
+
return {convert_value(x) for x in obj}
|
|
169
|
+
return obj
|
|
170
|
+
return {k: convert_value(v) for k, v in data}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def stream_as_dict(stream_obj):
|
|
174
|
+
"""Serialize stream information as dict."""
|
|
175
|
+
return asdict(
|
|
176
|
+
stream_obj,
|
|
177
|
+
dict_factory=_dict_factory)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Utility functions like saving and loading YAML files."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import yaml
|
|
5
|
+
|
|
6
|
+
from . import stream_as_dict, StreamInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load(filename: Path | str) -> list[StreamInfo]:
|
|
10
|
+
"""Load the yaml file and return it as list of StreamInfo."""
|
|
11
|
+
with open(filename, 'r', encoding='utf-8') as data:
|
|
12
|
+
yaml_data = yaml.safe_load(data)
|
|
13
|
+
return [StreamInfo(**stream) for stream in yaml_data]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def save(data: list[StreamInfo], filename: Path | str) -> None:
|
|
17
|
+
"""Save list of StreamInfo to yaml file."""
|
|
18
|
+
yaml_data = [stream_as_dict(s) for s in data]
|
|
19
|
+
with open(filename, 'w', encoding='utf-8') as data:
|
|
20
|
+
data.write(yaml.safe_dump(yaml_data))
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: video_stream_data
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Unified data format for video streams (devices, server, ...)
|
|
5
|
+
Author-email: Andreas Bresser <Andreas.Bresser@dfki.de>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://codeberg.org/brean/video_stream_data
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Provides-Extra: yaml
|
|
12
|
+
Requires-Dist: pyyaml; extra == "yaml"
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pytest; extra == "test"
|
|
15
|
+
Requires-Dist: pyyaml; extra == "test"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Video Stream Data
|
|
19
|
+
Dependency-free data structures for video streaming.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
Use **pip** to install. Its recommended to install in a virtual environment
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 -m venv video-streaming-pipeline
|
|
26
|
+
# install using system-site-packages (like opencv and pyudev)
|
|
27
|
+
source video-streaming-pipeline/bin/activate --system-site-packages
|
|
28
|
+
pip3 install video_stream_data
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Although not recommended a user installation with `--break-system-packages` would also be okay as it does not have any dependencies.
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
Simply import the data classes you want to have, you can use the parser to load the video stream settings from a file (see `test/`-folder).
|
|
35
|
+
|
|
36
|
+
You can use the data structure to manually create any kind of video stream.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
classDiagram
|
|
41
|
+
direction LR
|
|
42
|
+
|
|
43
|
+
class StreamControlType {
|
|
44
|
+
<<enumeration>>
|
|
45
|
+
+INT: str = 'int'
|
|
46
|
+
+BOOL: str = 'bool'
|
|
47
|
+
+MENU: str = 'menu'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class Encoding {
|
|
51
|
+
<<enumeration>>
|
|
52
|
+
+PASSTHROUGH: str = 'passthrough'
|
|
53
|
+
+BGR: str = 'bgr'
|
|
54
|
+
+GREY: str = 'grey'
|
|
55
|
+
+GREY16: str = 'grey16'
|
|
56
|
+
+GRB: str = 'rbg'
|
|
57
|
+
+BGRA: str = 'bgra'
|
|
58
|
+
+RGBA: str = 'rgba'
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class Codec {
|
|
63
|
+
<<enumeration>>
|
|
64
|
+
+MJPEG: str = 'MJPEG' # Motion-JPEG
|
|
65
|
+
+YUYV: str = 'YUYV' # e.g. YUYV 4:2:2
|
|
66
|
+
+XVID: str = 'XVID' # e.g. mp4 files
|
|
67
|
+
+H264: str = 'H264'
|
|
68
|
+
+UNKNOWN: str = 'unknown'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class StreamControl {
|
|
72
|
+
+type: StreamControlType
|
|
73
|
+
+name: str
|
|
74
|
+
+writable: bool = False
|
|
75
|
+
+min: float = 0
|
|
76
|
+
+max: float = 0
|
|
77
|
+
+step: int = 1
|
|
78
|
+
+value: float | bool | None = None
|
|
79
|
+
+options: list = []
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
class StreamValue {
|
|
83
|
+
# desired video resolution
|
|
84
|
+
+resolution: list[int]
|
|
85
|
+
+fps: float # desired FPS by user
|
|
86
|
+
# desired configuration
|
|
87
|
+
+controls: dict[str, int | float | bool | None]
|
|
88
|
+
+encoding: Encoding = Encoding.PASSTHROUGH
|
|
89
|
+
+codec: Codec = Codec.UNKNOWN
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
class StreamInfo {
|
|
93
|
+
+name: str
|
|
94
|
+
+stream_path: str
|
|
95
|
+
+driver: str
|
|
96
|
+
+codec: list[str]
|
|
97
|
+
+controls: list[StreamControl]
|
|
98
|
+
+resolutions: list[int, int]
|
|
99
|
+
+max_fps: float
|
|
100
|
+
+values: StreamValue
|
|
101
|
+
+backend: str = 'v4l2'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
StreamControlType <|-- StreamControl
|
|
105
|
+
Encoding <|-- StreamValue
|
|
106
|
+
Codec <|-- StreamValue
|
|
107
|
+
StreamControl --> StreamInfo : controls
|
|
108
|
+
StreamValue --> StreamInfo : values
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
*Fig. 1: Data Model for the StreamInfo generated from any device*
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
video_stream_data/__init__.py,sha256=RtVgtl5dbF-aLrOFPHOgLGZcYbVZez02R-gYjBUoWWU,4795
|
|
2
|
+
video_stream_data/utils.py,sha256=4jC2OxKuFFN6mbEHIUv70xBXPdS-DmLW_66KlB_e5Tk,681
|
|
3
|
+
video_stream_data-0.1.6.dist-info/licenses/LICENSE,sha256=9eS7gmlDqQI_plfhhfriVsVbRBzCzIjH1QH0qSyNB40,1502
|
|
4
|
+
video_stream_data-0.1.6.dist-info/METADATA,sha256=14AybSQ-7V9JtYicEeTd65_DTe9L-rDzstXrRqA5f2o,3097
|
|
5
|
+
video_stream_data-0.1.6.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
6
|
+
video_stream_data-0.1.6.dist-info/top_level.txt,sha256=iH0zMRQNzh7YijSv9kSwoX3kXQzj90T99BYZYnDywu4,18
|
|
7
|
+
video_stream_data-0.1.6.dist-info/RECORD,,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Andreas Bresser
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
video_stream_data
|