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,259 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import warnings
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
import cv2
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from .video_capture_base import VideoCaptureBase
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class VideoFileCapture(VideoCaptureBase):
|
|
15
|
+
has_discovery = False # Uses file paths, not discoverable devices
|
|
16
|
+
supports_exposure = False # Video files have no controllable exposure
|
|
17
|
+
supports_gain = False # Video files have no controllable gain
|
|
18
|
+
|
|
19
|
+
"""Video file capture using OpenCV."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
source: str,
|
|
24
|
+
*,
|
|
25
|
+
loop: bool = False,
|
|
26
|
+
real_time: bool = True,
|
|
27
|
+
width: Optional[int] = None,
|
|
28
|
+
height: Optional[int] = None,
|
|
29
|
+
fps: Optional[float] = None,
|
|
30
|
+
**kwargs,
|
|
31
|
+
):
|
|
32
|
+
"""Initialize the video file capture.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
source: Path to the video file.
|
|
36
|
+
loop: Restart playback from the beginning when the end is reached.
|
|
37
|
+
real_time: Play at the file's native frame rate (disable for
|
|
38
|
+
fastest processing).
|
|
39
|
+
width: Optional target frame width (applied on :meth:`connect`).
|
|
40
|
+
height: Optional target frame height (applied on :meth:`connect`).
|
|
41
|
+
fps: Optional target frame rate (applied on :meth:`connect`).
|
|
42
|
+
**kwargs: Additional passthrough options stored on ``self.config``.
|
|
43
|
+
"""
|
|
44
|
+
# Forward the promoted options back into ``self.config`` so behaviour
|
|
45
|
+
# that reads them (``connect()`` for width/height/fps) is preserved.
|
|
46
|
+
for _key, _val in (
|
|
47
|
+
("loop", loop),
|
|
48
|
+
("real_time", real_time),
|
|
49
|
+
("width", width),
|
|
50
|
+
("height", height),
|
|
51
|
+
("fps", fps),
|
|
52
|
+
):
|
|
53
|
+
if _val is not None:
|
|
54
|
+
kwargs[_key] = _val
|
|
55
|
+
super().__init__(source, **kwargs)
|
|
56
|
+
self.cap = None
|
|
57
|
+
self.loop = loop
|
|
58
|
+
self.real_time = real_time
|
|
59
|
+
self.time_of_last_frame = 0.0
|
|
60
|
+
|
|
61
|
+
def connect(self) -> bool:
|
|
62
|
+
"""Connect to video file."""
|
|
63
|
+
try:
|
|
64
|
+
self.cap = cv2.VideoCapture(self.source)
|
|
65
|
+
if not self.cap.isOpened():
|
|
66
|
+
logger.error(f"Failed to open video file {self.source}")
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
# Set additional parameters if provided
|
|
70
|
+
if "width" in self.config:
|
|
71
|
+
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, self.config["width"])
|
|
72
|
+
if "height" in self.config:
|
|
73
|
+
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, self.config["height"])
|
|
74
|
+
if "fps" in self.config:
|
|
75
|
+
self.cap.set(cv2.CAP_PROP_FPS, self.config["fps"])
|
|
76
|
+
|
|
77
|
+
self.is_connected = True
|
|
78
|
+
logger.info(f"Connected to video file {self.source}")
|
|
79
|
+
return True
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.error(f"Error connecting to video file: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def disconnect(self) -> bool:
|
|
85
|
+
"""Disconnect from video file."""
|
|
86
|
+
try:
|
|
87
|
+
if self.cap is not None:
|
|
88
|
+
self.cap.release()
|
|
89
|
+
self.cap = None
|
|
90
|
+
self.is_connected = False
|
|
91
|
+
logger.info("Disconnected from video file")
|
|
92
|
+
return True
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Error disconnecting from video file: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def _read_implementation(self) -> tuple[bool, Optional[np.ndarray]]:
|
|
98
|
+
"""
|
|
99
|
+
Read a single frame from the video file.
|
|
100
|
+
Returns:
|
|
101
|
+
Tuple[bool, Optional[np.ndarray]]: (success, frame)
|
|
102
|
+
"""
|
|
103
|
+
if not self.is_connected or self.cap is None:
|
|
104
|
+
return False, None
|
|
105
|
+
# Add delay for real-time playback simulation
|
|
106
|
+
if self.real_time:
|
|
107
|
+
video_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
108
|
+
if video_fps > 0:
|
|
109
|
+
frame_duration = 1.0 / video_fps
|
|
110
|
+
current_time = time.time()
|
|
111
|
+
elapsed = current_time - self.time_of_last_frame
|
|
112
|
+
if elapsed < frame_duration:
|
|
113
|
+
time.sleep(frame_duration - elapsed)
|
|
114
|
+
self.time_of_last_frame = time.time()
|
|
115
|
+
ret, frame = self.cap.read()
|
|
116
|
+
# If we've reached the end of the video and looping is enabled
|
|
117
|
+
if not ret and self.loop:
|
|
118
|
+
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
|
119
|
+
ret, frame = self.cap.read()
|
|
120
|
+
if self.real_time:
|
|
121
|
+
self.time_of_last_frame = time.time()
|
|
122
|
+
return ret, frame if ret else None
|
|
123
|
+
|
|
124
|
+
def set_exposure(self, value: float) -> bool:
|
|
125
|
+
"""Set exposure (not applicable for video files)."""
|
|
126
|
+
logger.warning("Exposure control not applicable for video files")
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def get_exposure(self) -> Optional[float]:
|
|
130
|
+
"""Get exposure (not applicable for video files)."""
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def set_gain(self, value: float) -> bool:
|
|
134
|
+
"""Set gain (not applicable for video files)."""
|
|
135
|
+
logger.warning("Gain control not applicable for video files")
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def get_gain(self) -> Optional[float]:
|
|
139
|
+
"""Get gain (not applicable for video files)."""
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def enable_auto_exposure(self, enable: bool = True) -> bool:
|
|
143
|
+
"""
|
|
144
|
+
Enable or disable auto exposure (not applicable for video files).
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
enable: True to enable, False to disable
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
bool: Always False for video files
|
|
151
|
+
"""
|
|
152
|
+
logger.warning("Auto exposure control not applicable for video files")
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
def set_frame_size(self, width: int, height: int) -> bool:
|
|
156
|
+
"""Set frame size (not applicable for video files)."""
|
|
157
|
+
logger.warning("Setting resolution is not applicable for video files")
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def discover(cls) -> list:
|
|
162
|
+
"""
|
|
163
|
+
Discover method for video file capture.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
list: Empty list, as discovery is not applicable for file-based sources.
|
|
167
|
+
Use this class directly with file paths as the source parameter.
|
|
168
|
+
"""
|
|
169
|
+
# Video file capture doesn't discover devices - it works with file paths
|
|
170
|
+
logger.info("VideoFileCapture uses file paths as sources, not discoverable devices.")
|
|
171
|
+
return []
|
|
172
|
+
|
|
173
|
+
@classmethod
|
|
174
|
+
def get_config_schema(cls) -> dict[str, Any]:
|
|
175
|
+
"""Get configuration schema for video file capture"""
|
|
176
|
+
warnings.warn(
|
|
177
|
+
"get_config_schema() is deprecated and will be removed in a future release; "
|
|
178
|
+
"UI form schemas belong in the consuming application.",
|
|
179
|
+
DeprecationWarning,
|
|
180
|
+
stacklevel=2,
|
|
181
|
+
)
|
|
182
|
+
return {
|
|
183
|
+
"title": "Video File Configuration",
|
|
184
|
+
"description": "Configure video file playback settings",
|
|
185
|
+
"fields": [
|
|
186
|
+
{
|
|
187
|
+
"name": "source",
|
|
188
|
+
"label": "Video File Path",
|
|
189
|
+
"type": "text",
|
|
190
|
+
"placeholder": "C:/path/to/video.mp4",
|
|
191
|
+
"description": (
|
|
192
|
+
"Full path to video file (supports .mp4, .avi, .mov, .mkv, "
|
|
193
|
+
".wmv, .flv, .webm)"
|
|
194
|
+
),
|
|
195
|
+
"required": True,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "loop",
|
|
199
|
+
"label": "Loop Playback",
|
|
200
|
+
"type": "checkbox",
|
|
201
|
+
"description": "Restart video from beginning when it ends",
|
|
202
|
+
"required": False,
|
|
203
|
+
"default": False,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"name": "real_time",
|
|
207
|
+
"label": "Real-time Playback",
|
|
208
|
+
"type": "checkbox",
|
|
209
|
+
"description": (
|
|
210
|
+
"Play video at original frame rate (disable for fastest processing)"
|
|
211
|
+
),
|
|
212
|
+
"required": False,
|
|
213
|
+
"default": True,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "width",
|
|
217
|
+
"label": "Width",
|
|
218
|
+
"type": "number",
|
|
219
|
+
"min": 160,
|
|
220
|
+
"max": 4096,
|
|
221
|
+
"placeholder": "1920",
|
|
222
|
+
"description": "Resize frame width (optional)",
|
|
223
|
+
"required": False,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"name": "height",
|
|
227
|
+
"label": "Height",
|
|
228
|
+
"type": "number",
|
|
229
|
+
"min": 120,
|
|
230
|
+
"max": 2160,
|
|
231
|
+
"placeholder": "1080",
|
|
232
|
+
"description": "Resize frame height (optional)",
|
|
233
|
+
"required": False,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
if __name__ == "__main__":
|
|
240
|
+
# Example usage
|
|
241
|
+
video_file = "path/to/your/video.mp4" # Replace with your video file path
|
|
242
|
+
camera = VideoFileCapture(source=video_file, loop=True, real_time=True)
|
|
243
|
+
|
|
244
|
+
if camera.connect():
|
|
245
|
+
print("Webcam connected successfully.")
|
|
246
|
+
print(f"Exposure: {camera.get_exposure()}")
|
|
247
|
+
print(f"Gain: {camera.get_gain()}")
|
|
248
|
+
print(f"Frame size: {camera.get_frame_size()}")
|
|
249
|
+
|
|
250
|
+
# Read a few frames
|
|
251
|
+
while camera.is_connected:
|
|
252
|
+
ret, frame = camera.read()
|
|
253
|
+
if ret and frame is not None:
|
|
254
|
+
cv2.imshow("Webcam", frame)
|
|
255
|
+
if cv2.waitKey(1) & 0xFF == ord("q"):
|
|
256
|
+
break
|
|
257
|
+
camera.disconnect()
|
|
258
|
+
else:
|
|
259
|
+
print("Failed to connect to webcam.")
|