framesource 0.3.0__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.
- frame_processors/__init__.py +37 -0
- frame_source/__init__.py +50 -0
- framesource/__init__.py +93 -0
- framesource/_msmf_config.py +26 -0
- framesource/_version.py +24 -0
- framesource/discovery.py +109 -0
- framesource/errors.py +82 -0
- framesource/factory.py +290 -0
- framesource/processors/__init__.py +34 -0
- framesource/processors/equirectangular360_processor.py +577 -0
- framesource/processors/fisheye2equirectangular_processor.py +328 -0
- framesource/processors/frame_processor.py +30 -0
- framesource/processors/hyperspectral_processor.py +23 -0
- framesource/processors/realsense_depth_processor.py +90 -0
- framesource/py.typed +0 -0
- framesource/sources/__init__.py +40 -0
- framesource/sources/audiospectrogram_capture.py +1068 -0
- framesource/sources/basler_capture.py +477 -0
- framesource/sources/folder_capture.py +920 -0
- framesource/sources/genicam_capture.py +681 -0
- framesource/sources/huateng_capture.py +245 -0
- framesource/sources/ipcamera_capture.py +254 -0
- framesource/sources/mvsdk.py +2454 -0
- framesource/sources/realsense_capture.py +565 -0
- framesource/sources/screen_capture.py +800 -0
- framesource/sources/video_capture_base.py +560 -0
- framesource/sources/video_file_capture.py +259 -0
- framesource/sources/webcam_capture.py +511 -0
- framesource/sources/ximea_capture.py +299 -0
- framesource/threading_utils.py +790 -0
- framesource-0.3.0.dist-info/METADATA +787 -0
- framesource-0.3.0.dist-info/RECORD +37 -0
- framesource-0.3.0.dist-info/WHEEL +5 -0
- framesource-0.3.0.dist-info/licenses/LICENSE +21 -0
- framesource-0.3.0.dist-info/scm_file_list.json +276 -0
- framesource-0.3.0.dist-info/scm_version.json +8 -0
- framesource-0.3.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import platform
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ..errors import MissingDependencyError
|
|
8
|
+
from .video_capture_base import VideoCaptureBase
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
# The Huateng/MindVision SDK loads a vendor driver DLL at import time, which is
|
|
13
|
+
# only present when the camera drivers are installed. Guard the import so the
|
|
14
|
+
# module can still be imported (and the class referenced) without the SDK.
|
|
15
|
+
_MVSDK_IMPORT_ERROR: Optional[str] = None
|
|
16
|
+
try:
|
|
17
|
+
from . import mvsdk
|
|
18
|
+
except (ImportError, OSError) as e:
|
|
19
|
+
mvsdk = None
|
|
20
|
+
_MVSDK_IMPORT_ERROR = str(e)
|
|
21
|
+
logger.warning(f"Huateng mvsdk SDK unavailable: {e}")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HuatengCapture(VideoCaptureBase):
|
|
25
|
+
supports_exposure = True
|
|
26
|
+
supports_gain = True
|
|
27
|
+
|
|
28
|
+
"""Huateng camera capture using mvsdk."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, source: Any = None, *, is_mono: Optional[bool] = None, **kwargs):
|
|
31
|
+
"""Initialize the Huateng (MindVision) capture.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
source: Reserved for future multi-device selection; the first
|
|
35
|
+
enumerated camera is currently always used regardless of
|
|
36
|
+
this value.
|
|
37
|
+
is_mono: Stored for parity with other vendor sources (default:
|
|
38
|
+
False). Whether the stream is mono or color is auto-detected
|
|
39
|
+
from the device capability in :meth:`connect`, so this flag
|
|
40
|
+
currently has no effect.
|
|
41
|
+
**kwargs: Additional passthrough options stored on ``self.config``.
|
|
42
|
+
"""
|
|
43
|
+
# Preserve the historical ``self.config`` contents: only forward an
|
|
44
|
+
# explicitly-provided value.
|
|
45
|
+
if is_mono is not None:
|
|
46
|
+
kwargs["is_mono"] = is_mono
|
|
47
|
+
super().__init__(source, **kwargs)
|
|
48
|
+
self.hCamera = -1
|
|
49
|
+
self.nDev = 0
|
|
50
|
+
self.capability = None
|
|
51
|
+
self.pFrameBuffer = None
|
|
52
|
+
self.frame = None
|
|
53
|
+
self.DevInfo = None
|
|
54
|
+
self.is_mono = self.config.get("is_mono", False)
|
|
55
|
+
self.current_exp = 0
|
|
56
|
+
self.current_gain = 0
|
|
57
|
+
self.prop_frame_height = None
|
|
58
|
+
self.prop_frame_width = None
|
|
59
|
+
self.is_new_frame = False
|
|
60
|
+
self.read_count = 0
|
|
61
|
+
|
|
62
|
+
def connect(self) -> bool:
|
|
63
|
+
if mvsdk is None:
|
|
64
|
+
raise MissingDependencyError(
|
|
65
|
+
"mvsdk (Huateng/MindVision SDK)",
|
|
66
|
+
extra=None,
|
|
67
|
+
details=_MVSDK_IMPORT_ERROR,
|
|
68
|
+
)
|
|
69
|
+
try:
|
|
70
|
+
self.DevList = mvsdk.CameraEnumerateDevice()
|
|
71
|
+
self.nDev = len(self.DevList)
|
|
72
|
+
if self.nDev < 1:
|
|
73
|
+
logger.error("No Huateng cameras found!")
|
|
74
|
+
return False
|
|
75
|
+
i = 0 # Always pick the first camera for now
|
|
76
|
+
self.DevInfo = self.DevList[i]
|
|
77
|
+
self.hCamera = mvsdk.CameraInit(self.DevInfo, -1, -1)
|
|
78
|
+
self.capability = mvsdk.CameraGetCapability(self.hCamera)
|
|
79
|
+
monoCamera = self.capability.sIspCapacity.bMonoSensor != 0
|
|
80
|
+
if monoCamera:
|
|
81
|
+
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
|
82
|
+
else:
|
|
83
|
+
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
|
84
|
+
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
|
85
|
+
mvsdk.CameraSetAeState(self.hCamera, 1)
|
|
86
|
+
mvsdk.CameraPlay(self.hCamera)
|
|
87
|
+
FrameBufferSize = (
|
|
88
|
+
self.capability.sResolutionRange.iWidthMax
|
|
89
|
+
* self.capability.sResolutionRange.iHeightMax
|
|
90
|
+
* (1 if monoCamera else 3)
|
|
91
|
+
)
|
|
92
|
+
self.pFrameBuffer = mvsdk.CameraAlignMalloc(FrameBufferSize, 16)
|
|
93
|
+
self.prop_frame_height = self.capability.sResolutionRange.iHeightMax
|
|
94
|
+
self.prop_frame_width = self.capability.sResolutionRange.iWidthMax
|
|
95
|
+
self.is_connected = True
|
|
96
|
+
logger.info("Connected to Huateng camera")
|
|
97
|
+
return True
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"Error connecting to Huateng camera: {e}")
|
|
100
|
+
return False
|
|
101
|
+
|
|
102
|
+
def disconnect(self) -> bool:
|
|
103
|
+
try:
|
|
104
|
+
if self.hCamera != -1:
|
|
105
|
+
mvsdk.CameraUnInit(self.hCamera)
|
|
106
|
+
self.hCamera = -1
|
|
107
|
+
if self.pFrameBuffer is not None:
|
|
108
|
+
mvsdk.CameraAlignFree(self.pFrameBuffer)
|
|
109
|
+
self.pFrameBuffer = None
|
|
110
|
+
self.is_connected = False
|
|
111
|
+
logger.info("Disconnected from Huateng camera")
|
|
112
|
+
return True
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Error disconnecting from Huateng camera: {e}")
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
|
|
118
|
+
if not self.is_connected or self.hCamera == -1:
|
|
119
|
+
return False, None
|
|
120
|
+
try:
|
|
121
|
+
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 10)
|
|
122
|
+
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.pFrameBuffer, FrameHead)
|
|
123
|
+
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
|
124
|
+
if platform.system() == "Windows":
|
|
125
|
+
mvsdk.CameraFlipFrameBuffer(self.pFrameBuffer, FrameHead, 1)
|
|
126
|
+
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.pFrameBuffer)
|
|
127
|
+
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
|
128
|
+
frame = frame.reshape(
|
|
129
|
+
(
|
|
130
|
+
FrameHead.iHeight,
|
|
131
|
+
FrameHead.iWidth,
|
|
132
|
+
1 if FrameHead.uiMediaType == mvsdk.CAMERA_MEDIA_TYPE_MONO8 else 3,
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
return True, frame
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Error reading from Huateng camera: {e}")
|
|
138
|
+
return False, None
|
|
139
|
+
|
|
140
|
+
def set_exposure(self, value: float) -> bool:
|
|
141
|
+
try:
|
|
142
|
+
mvsdk.CameraSetAeState(self.hCamera, False)
|
|
143
|
+
mvsdk.CameraSetExposureTime(self.hCamera, int(value))
|
|
144
|
+
self.current_exp = int(value)
|
|
145
|
+
return True
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"Error setting exposure: {e}")
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
def get_exposure(self) -> Optional[float]:
|
|
151
|
+
try:
|
|
152
|
+
return float(mvsdk.CameraGetExposureTime(self.hCamera))
|
|
153
|
+
except Exception:
|
|
154
|
+
return self.current_exp
|
|
155
|
+
|
|
156
|
+
def set_gain(self, value: float) -> bool:
|
|
157
|
+
try:
|
|
158
|
+
mvsdk.CameraSetAeState(self.hCamera, False)
|
|
159
|
+
mvsdk.CameraSetAnalogGainX(self.hCamera, int(value))
|
|
160
|
+
self.current_gain = int(value)
|
|
161
|
+
return True
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"Error setting gain: {e}")
|
|
164
|
+
return False
|
|
165
|
+
|
|
166
|
+
def get_gain(self) -> Optional[float]:
|
|
167
|
+
try:
|
|
168
|
+
return float(mvsdk.CameraGetAnalogGainX(self.hCamera))
|
|
169
|
+
except Exception:
|
|
170
|
+
return self.current_gain
|
|
171
|
+
|
|
172
|
+
def enable_auto_exposure(self, enable: bool = True) -> bool:
|
|
173
|
+
try:
|
|
174
|
+
mvsdk.CameraSetAeState(self.hCamera, bool(enable))
|
|
175
|
+
logger.info(f"Set Huateng auto exposure to {enable}")
|
|
176
|
+
return True
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.error(f"Error setting auto exposure: {e}")
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def get_exposure_range(self) -> Optional[tuple[float, float]]:
|
|
182
|
+
try:
|
|
183
|
+
min_exp = 5
|
|
184
|
+
max_exp = 31 * 1000
|
|
185
|
+
return (min_exp, max_exp)
|
|
186
|
+
except Exception:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def get_gain_range(self) -> Optional[tuple[float, float]]:
|
|
190
|
+
try:
|
|
191
|
+
min_gain = 2
|
|
192
|
+
max_gain = 6
|
|
193
|
+
return (min_gain, max_gain)
|
|
194
|
+
except Exception:
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
def get_frame_size(self) -> Optional[tuple[int, int]]:
|
|
198
|
+
if self.prop_frame_width and self.prop_frame_height:
|
|
199
|
+
return (self.prop_frame_width, self.prop_frame_height)
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def set_frame_size(self, width: int, height: int) -> bool:
|
|
203
|
+
try:
|
|
204
|
+
imageres = mvsdk.tSdkImageResolution(width, height)
|
|
205
|
+
mvsdk.CameraSetImageResolution(self.hCamera, imageres)
|
|
206
|
+
self.prop_frame_width = width
|
|
207
|
+
self.prop_frame_height = height
|
|
208
|
+
logger.info(f"Set Huateng camera resolution to {width}x{height}")
|
|
209
|
+
return True
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Error setting Huateng camera resolution: {e}")
|
|
212
|
+
return False
|
|
213
|
+
|
|
214
|
+
def get_fps(self) -> Optional[float]:
|
|
215
|
+
# Not directly supported in mvsdk, return None
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def set_fps(self, fps: float) -> bool:
|
|
219
|
+
# Not directly supported in mvsdk, return False
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
# Example usage
|
|
225
|
+
import cv2
|
|
226
|
+
|
|
227
|
+
camera = HuatengCapture(is_mono=False)
|
|
228
|
+
if camera.connect():
|
|
229
|
+
print("Webcam connected successfully.")
|
|
230
|
+
print(f"Exposure: {camera.get_exposure()}")
|
|
231
|
+
print(f"Gain: {camera.get_gain()}")
|
|
232
|
+
print(f"Frame size: {camera.get_frame_size()}")
|
|
233
|
+
|
|
234
|
+
# Read a few frames
|
|
235
|
+
while camera.is_connected:
|
|
236
|
+
ret, frame = camera.read()
|
|
237
|
+
if ret and frame is not None:
|
|
238
|
+
cv2.imshow("camera", frame)
|
|
239
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
240
|
+
break
|
|
241
|
+
|
|
242
|
+
camera.stop()
|
|
243
|
+
camera.disconnect()
|
|
244
|
+
else:
|
|
245
|
+
print("Failed to connect to webcam.")
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import cv2
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
from .video_capture_base import VideoCaptureBase
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IPCameraCapture(VideoCaptureBase):
|
|
14
|
+
has_discovery = False # Requires manual URL/credential configuration
|
|
15
|
+
supports_exposure = False # set_exposure() is a no-op stub (returns False)
|
|
16
|
+
supports_gain = False # set_gain() is a no-op stub (returns False)
|
|
17
|
+
|
|
18
|
+
"""IP Camera capture using OpenCV with RTSP/HTTP streams."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
source: str,
|
|
23
|
+
username: Optional[str] = None,
|
|
24
|
+
password: Optional[str] = None,
|
|
25
|
+
*,
|
|
26
|
+
width: Optional[int] = None,
|
|
27
|
+
height: Optional[int] = None,
|
|
28
|
+
fps: Optional[float] = None,
|
|
29
|
+
**kwargs,
|
|
30
|
+
):
|
|
31
|
+
"""Initialize the IP camera capture.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
source: RTSP/HTTP stream URL.
|
|
35
|
+
username: Optional login username; when given together with
|
|
36
|
+
``password`` it is injected into the stream URL.
|
|
37
|
+
password: Optional login password (see ``username``).
|
|
38
|
+
width: Optional frame width hint (if supported by the camera),
|
|
39
|
+
stored on ``self.config``.
|
|
40
|
+
height: Optional frame height hint (see ``width``).
|
|
41
|
+
fps: Optional expected frame rate (informational), stored on
|
|
42
|
+
``self.config``.
|
|
43
|
+
**kwargs: Additional passthrough options stored on ``self.config``.
|
|
44
|
+
"""
|
|
45
|
+
# Forward the promoted options into ``self.config`` exactly as before
|
|
46
|
+
# (they were previously accepted via ``**kwargs``); only include
|
|
47
|
+
# explicitly-provided values so config keys are unchanged.
|
|
48
|
+
for _key, _val in (("width", width), ("height", height), ("fps", fps)):
|
|
49
|
+
if _val is not None:
|
|
50
|
+
kwargs[_key] = _val
|
|
51
|
+
super().__init__(source, **kwargs)
|
|
52
|
+
self.username = username
|
|
53
|
+
self.password = password
|
|
54
|
+
self.cap = None
|
|
55
|
+
if username is not None and password is not None:
|
|
56
|
+
self.stream_url = self._build_stream_url()
|
|
57
|
+
else:
|
|
58
|
+
self.stream_url = source
|
|
59
|
+
|
|
60
|
+
def _build_stream_url(self) -> str:
|
|
61
|
+
"""Build stream URL with authentication if provided."""
|
|
62
|
+
if self.username and self.password:
|
|
63
|
+
# Insert credentials into URL
|
|
64
|
+
if "://" in self.source:
|
|
65
|
+
protocol, rest = self.source.split("://", 1)
|
|
66
|
+
return f"{protocol}://{self.username}:{self.password}@{rest}"
|
|
67
|
+
return self.source
|
|
68
|
+
|
|
69
|
+
def connect(self) -> bool:
|
|
70
|
+
"""Connect to IP camera."""
|
|
71
|
+
try:
|
|
72
|
+
self.cap = cv2.VideoCapture(self.stream_url)
|
|
73
|
+
if not self.cap.isOpened():
|
|
74
|
+
logger.error(f"Failed to open IP camera stream: {self.stream_url}")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
# Set buffer size to reduce latency
|
|
78
|
+
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
|
79
|
+
|
|
80
|
+
self.is_connected = True
|
|
81
|
+
logger.info(f"Connected to IP camera: {self.source}")
|
|
82
|
+
return True
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error(f"Error connecting to IP camera: {e}")
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def disconnect(self) -> bool:
|
|
88
|
+
"""Disconnect from IP camera."""
|
|
89
|
+
try:
|
|
90
|
+
if self.cap is not None:
|
|
91
|
+
self.cap.release()
|
|
92
|
+
self.cap = None
|
|
93
|
+
self.is_connected = False
|
|
94
|
+
logger.info("Disconnected from IP camera")
|
|
95
|
+
return True
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Error disconnecting from IP camera: {e}")
|
|
98
|
+
return False
|
|
99
|
+
|
|
100
|
+
def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
|
|
101
|
+
"""
|
|
102
|
+
Read a single frame from the IP camera.
|
|
103
|
+
Returns:
|
|
104
|
+
Tuple[bool, Optional[np.ndarray]]: (success, frame)
|
|
105
|
+
"""
|
|
106
|
+
if not self.is_connected or self.cap is None:
|
|
107
|
+
return False, None
|
|
108
|
+
ret, frame = self.cap.read()
|
|
109
|
+
return ret, frame if ret else None
|
|
110
|
+
|
|
111
|
+
def set_exposure(self, value: float) -> bool:
|
|
112
|
+
"""Set exposure (may not be supported by all IP cameras)."""
|
|
113
|
+
logger.warning("Exposure control may not be supported by IP cameras")
|
|
114
|
+
self._exposure = value
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
def get_exposure(self) -> Optional[float]:
|
|
118
|
+
"""Get exposure."""
|
|
119
|
+
return self._exposure
|
|
120
|
+
|
|
121
|
+
def set_gain(self, value: float) -> bool:
|
|
122
|
+
"""Set gain (may not be supported by all IP cameras)."""
|
|
123
|
+
logger.warning("Gain control may not be supported by IP cameras")
|
|
124
|
+
self._gain = value
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def get_gain(self) -> Optional[float]:
|
|
128
|
+
"""Get gain."""
|
|
129
|
+
return self._gain
|
|
130
|
+
|
|
131
|
+
def enable_auto_exposure(self, enable: bool = True) -> bool:
|
|
132
|
+
"""
|
|
133
|
+
Enable or disable auto exposure (not generally supported for IP cameras).
|
|
134
|
+
"""
|
|
135
|
+
logger.warning("Auto exposure control may not be supported by IP cameras")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def set_frame_size(self, width: int, height: int) -> bool:
|
|
139
|
+
"""Set frame size (may not be supported by all IP cameras)."""
|
|
140
|
+
if not self.is_connected or self.cap is None:
|
|
141
|
+
return False
|
|
142
|
+
result1 = self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
|
|
143
|
+
result2 = self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
|
|
144
|
+
logger.info(
|
|
145
|
+
f"Set IP camera resolution to {width}x{height} (success: {result1 and result2})"
|
|
146
|
+
)
|
|
147
|
+
return result1 and result2
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def discover(cls) -> list:
|
|
151
|
+
"""
|
|
152
|
+
Discover method for IP camera capture.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
list: Empty list, as IP cameras require manual configuration with URLs/credentials.
|
|
156
|
+
Use this class directly with RTSP/HTTP URLs as the source parameter.
|
|
157
|
+
"""
|
|
158
|
+
# IP cameras require manual configuration - cannot be auto-discovered easily
|
|
159
|
+
logger.info("IPCameraCapture requires manual configuration with URLs and credentials.")
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
@classmethod
|
|
163
|
+
def get_config_schema(cls) -> dict[str, Any]:
|
|
164
|
+
"""Get configuration schema for IP camera capture"""
|
|
165
|
+
warnings.warn(
|
|
166
|
+
"get_config_schema() is deprecated and will be removed in a future release; "
|
|
167
|
+
"UI form schemas belong in the consuming application.",
|
|
168
|
+
DeprecationWarning,
|
|
169
|
+
stacklevel=2,
|
|
170
|
+
)
|
|
171
|
+
return {
|
|
172
|
+
"title": "IP Camera Configuration",
|
|
173
|
+
"description": "Configure IP camera with RTSP/HTTP stream settings",
|
|
174
|
+
"fields": [
|
|
175
|
+
{
|
|
176
|
+
"name": "source",
|
|
177
|
+
"label": "Stream URL",
|
|
178
|
+
"type": "text",
|
|
179
|
+
"placeholder": "rtsp://192.168.1.100:554/stream1",
|
|
180
|
+
"description": "RTSP or HTTP stream URL",
|
|
181
|
+
"required": True,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"name": "username",
|
|
185
|
+
"label": "Username",
|
|
186
|
+
"type": "text",
|
|
187
|
+
"placeholder": "admin",
|
|
188
|
+
"description": "Camera login username (optional)",
|
|
189
|
+
"required": False,
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"name": "password",
|
|
193
|
+
"label": "Password",
|
|
194
|
+
"type": "password",
|
|
195
|
+
"placeholder": "password",
|
|
196
|
+
"description": "Camera login password (optional)",
|
|
197
|
+
"required": False,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "width",
|
|
201
|
+
"label": "Width",
|
|
202
|
+
"type": "number",
|
|
203
|
+
"min": 160,
|
|
204
|
+
"max": 4096,
|
|
205
|
+
"placeholder": "1920",
|
|
206
|
+
"description": "Frame width in pixels (if supported)",
|
|
207
|
+
"required": False,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "height",
|
|
211
|
+
"label": "Height",
|
|
212
|
+
"type": "number",
|
|
213
|
+
"min": 120,
|
|
214
|
+
"max": 2160,
|
|
215
|
+
"placeholder": "1080",
|
|
216
|
+
"description": "Frame height in pixels (if supported)",
|
|
217
|
+
"required": False,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "fps",
|
|
221
|
+
"label": "Frame Rate (FPS)",
|
|
222
|
+
"type": "number",
|
|
223
|
+
"min": 1,
|
|
224
|
+
"max": 60,
|
|
225
|
+
"placeholder": "25",
|
|
226
|
+
"description": "Expected frame rate (informational)",
|
|
227
|
+
"required": False,
|
|
228
|
+
"default": 25,
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
# Example usage
|
|
236
|
+
camera = IPCameraCapture(
|
|
237
|
+
source="rtsp://192.168.1.153:554/h264Preview_01_sub", username="admin", password="password"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if camera.connect():
|
|
241
|
+
print("IP Camera connected successfully.")
|
|
242
|
+
print(f"Exposure: {camera.get_exposure()}")
|
|
243
|
+
print(f"Gain: {camera.get_gain()}")
|
|
244
|
+
print(f"Frame size: {camera.get_frame_size()}")
|
|
245
|
+
|
|
246
|
+
# Read a few frames
|
|
247
|
+
while camera.is_connected:
|
|
248
|
+
ret, frame = camera.read()
|
|
249
|
+
if ret:
|
|
250
|
+
cv2.imshow("IP Camera", frame) # type: ignore
|
|
251
|
+
if cv2.waitKey(1000) & 0xFF == ord("q"):
|
|
252
|
+
break
|
|
253
|
+
camera.stop()
|
|
254
|
+
camera.disconnect()
|