evileye 0.0.4__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.
Files changed (158) hide show
  1. evileye/__init__.py +88 -0
  2. evileye/capture/__init__.py +4 -0
  3. evileye/capture/video_capture.py +229 -0
  4. evileye/capture/video_capture_base.py +181 -0
  5. evileye/cli.py +494 -0
  6. evileye/cli_wrapper.py +24 -0
  7. evileye/configure.py +36 -0
  8. evileye/configure_wrapper.py +17 -0
  9. evileye/controller/__init__.py +2 -0
  10. evileye/controller/controller.py +844 -0
  11. evileye/core/__init__.py +12 -0
  12. evileye/core/base_class.py +105 -0
  13. evileye/core/frame.py +12 -0
  14. evileye/core/mp_control.py +39 -0
  15. evileye/core/mp_worker.py +41 -0
  16. evileye/core/pipeline_base.py +347 -0
  17. evileye/core/pipeline_processors.py +170 -0
  18. evileye/core/pipeline_simple.py +103 -0
  19. evileye/core/processor_base.py +84 -0
  20. evileye/core/processor_frame.py +30 -0
  21. evileye/core/processor_source.py +32 -0
  22. evileye/core/processor_step.py +51 -0
  23. evileye/create.py +195 -0
  24. evileye/create_wrapper.py +22 -0
  25. evileye/credentials_proto.json +17 -0
  26. evileye/database_config.json +110 -0
  27. evileye/database_controller/__init__.py +2 -0
  28. evileye/database_controller/database_controller_base.py +76 -0
  29. evileye/database_controller/database_controller_pg.py +467 -0
  30. evileye/database_controller/db_adapter.py +79 -0
  31. evileye/database_controller/db_adapter_cam_events.py +59 -0
  32. evileye/database_controller/db_adapter_fov_events.py +135 -0
  33. evileye/database_controller/db_adapter_objects.py +181 -0
  34. evileye/database_controller/db_adapter_zone_events.py +153 -0
  35. evileye/events_control/events_controller.py +77 -0
  36. evileye/events_control/events_processor.py +159 -0
  37. evileye/events_detectors/cam_events_detector.py +68 -0
  38. evileye/events_detectors/event.py +28 -0
  39. evileye/events_detectors/event_cameras.py +18 -0
  40. evileye/events_detectors/event_fov.py +23 -0
  41. evileye/events_detectors/event_zone.py +43 -0
  42. evileye/events_detectors/events_detector.py +42 -0
  43. evileye/events_detectors/fov_events_detector.py +160 -0
  44. evileye/events_detectors/zone.py +40 -0
  45. evileye/events_detectors/zone_events_detector.py +327 -0
  46. evileye/icons/add_zone.svg +17 -0
  47. evileye/icons/cam_history.svg +4 -0
  48. evileye/icons/delete_zone.svg +2 -0
  49. evileye/icons/display_zones.svg +2 -0
  50. evileye/icons/journal.svg +6 -0
  51. evileye/icons/run_app.svg +5 -0
  52. evileye/icons/save_icon.svg +4 -0
  53. evileye/icons/zone_polygon.svg +2 -0
  54. evileye/icons/zone_rect.svg +2 -0
  55. evileye/launch.py +495 -0
  56. evileye/launch_wrapper.py +17 -0
  57. evileye/models/yolov8n.pt +0 -0
  58. evileye/object_detector/__init__.py +8 -0
  59. evileye/object_detector/background_subtraction_base.py +14 -0
  60. evileye/object_detector/background_subtraction_gmm.py +48 -0
  61. evileye/object_detector/detection_thread_base.py +138 -0
  62. evileye/object_detector/detection_thread_yolo.py +51 -0
  63. evileye/object_detector/detection_thread_yolo_mp.py +53 -0
  64. evileye/object_detector/mp_worker_yolo.py +33 -0
  65. evileye/object_detector/object_detection_base.py +141 -0
  66. evileye/object_detector/object_detection_yolo.py +53 -0
  67. evileye/object_detector/object_detection_yolo_mp.py +47 -0
  68. evileye/object_multi_camera_tracker/__init__.py +6 -0
  69. evileye/object_multi_camera_tracker/custom_object_tracking.py +464 -0
  70. evileye/object_multi_camera_tracker/mctrack.py +118 -0
  71. evileye/object_multi_camera_tracker/object_multicam_tracking_base.py +66 -0
  72. evileye/object_tracker/__init__.py +3 -0
  73. evileye/object_tracker/object_tracking_base.py +79 -0
  74. evileye/object_tracker/object_tracking_botsort.py +183 -0
  75. evileye/object_tracker/object_tracking_impl.py +24 -0
  76. evileye/object_tracker/trackers/__init__.py +7 -0
  77. evileye/object_tracker/trackers/basetrack.py +105 -0
  78. evileye/object_tracker/trackers/bot_sort.py +267 -0
  79. evileye/object_tracker/trackers/byte_tracker.py +460 -0
  80. evileye/object_tracker/trackers/cfg/utils.py +14 -0
  81. evileye/object_tracker/trackers/encoder.py +11 -0
  82. evileye/object_tracker/trackers/onnx_encoder.py +97 -0
  83. evileye/object_tracker/trackers/sctrack.py +114 -0
  84. evileye/object_tracker/trackers/track.py +89 -0
  85. evileye/object_tracker/trackers/track_encoder.py +8 -0
  86. evileye/object_tracker/trackers/tracker_test.py +54 -0
  87. evileye/object_tracker/trackers/tracking_wrapper.py +46 -0
  88. evileye/object_tracker/trackers/utils/__init__.py +1 -0
  89. evileye/object_tracker/trackers/utils/gmc.py +367 -0
  90. evileye/object_tracker/trackers/utils/kalman_filter.py +360 -0
  91. evileye/object_tracker/trackers/utils/matching.py +138 -0
  92. evileye/object_tracker/tracking_results.py +44 -0
  93. evileye/objects_handler/labeling_manager.py +663 -0
  94. evileye/objects_handler/object_result.py +77 -0
  95. evileye/objects_handler/objects_handler.py +485 -0
  96. evileye/pipelines/__init__.py +4 -0
  97. evileye/pipelines/pipeline_capture.py +216 -0
  98. evileye/pipelines/pipeline_surveillance.py +167 -0
  99. evileye/preprocessing/__init__.py +4 -0
  100. evileye/preprocessing/preprocessing_base.py +70 -0
  101. evileye/preprocessing/preprocessing_factory.py +25 -0
  102. evileye/preprocessing/preprocessing_pipeline.py +50 -0
  103. evileye/preprocessing/steps/__init__.py +6 -0
  104. evileye/preprocessing/steps/step_abstract.py +24 -0
  105. evileye/preprocessing/steps/step_clahe.py +21 -0
  106. evileye/preprocessing/steps/step_inpaint.py +24 -0
  107. evileye/preprocessing/steps/step_input.py +12 -0
  108. evileye/preprocessing/steps/step_normalize.py +13 -0
  109. evileye/preprocessing/steps/step_output.py +12 -0
  110. evileye/process.py +123 -0
  111. evileye/process_wrapper.py +19 -0
  112. evileye/samples_configs/multi_videos.json +147 -0
  113. evileye/samples_configs/pipeline_capture.json +80 -0
  114. evileye/samples_configs/single_ip_camera.json +114 -0
  115. evileye/samples_configs/single_video.json +110 -0
  116. evileye/samples_configs/single_video_split.json +213 -0
  117. evileye/samples_configs/single_video_with_detplanes.json +148 -0
  118. evileye/utils/__init__.py +0 -0
  119. evileye/utils/download_samples.py +167 -0
  120. evileye/utils/threading_events.py +20 -0
  121. evileye/utils/utils.py +739 -0
  122. evileye/visualization_modules/__init__.py +0 -0
  123. evileye/visualization_modules/configurer/configurer_tabs/database_tab.py +137 -0
  124. evileye/visualization_modules/configurer/configurer_tabs/detector_tab.py +124 -0
  125. evileye/visualization_modules/configurer/configurer_tabs/detector_widget.py +141 -0
  126. evileye/visualization_modules/configurer/configurer_tabs/events_tab.py +184 -0
  127. evileye/visualization_modules/configurer/configurer_tabs/handler_tab.py +99 -0
  128. evileye/visualization_modules/configurer/configurer_tabs/src_tab.py +167 -0
  129. evileye/visualization_modules/configurer/configurer_tabs/src_widget.py +378 -0
  130. evileye/visualization_modules/configurer/configurer_tabs/tracker_tab.py +135 -0
  131. evileye/visualization_modules/configurer/configurer_tabs/tracker_widget.py +208 -0
  132. evileye/visualization_modules/configurer/configurer_tabs/visualizer_tab.py +145 -0
  133. evileye/visualization_modules/configurer/configurer_window.py +349 -0
  134. evileye/visualization_modules/configurer/db_connection_window.py +100 -0
  135. evileye/visualization_modules/configurer/jobs_history_journal.py +263 -0
  136. evileye/visualization_modules/configurer/parameters_processing.py +54 -0
  137. evileye/visualization_modules/db_journal.py +115 -0
  138. evileye/visualization_modules/events_journal.py +487 -0
  139. evileye/visualization_modules/events_journal_json.py +546 -0
  140. evileye/visualization_modules/handler_journal_view.py +447 -0
  141. evileye/visualization_modules/journal_adapters/jadapter_base.py +48 -0
  142. evileye/visualization_modules/journal_adapters/jadapter_cam_events.py +28 -0
  143. evileye/visualization_modules/journal_adapters/jadapter_fov_events.py +27 -0
  144. evileye/visualization_modules/journal_adapters/jadapter_zone_events.py +17 -0
  145. evileye/visualization_modules/journal_data_source.py +33 -0
  146. evileye/visualization_modules/journal_data_source_json.py +200 -0
  147. evileye/visualization_modules/main_window.py +378 -0
  148. evileye/visualization_modules/table_data_thread.py +61 -0
  149. evileye/visualization_modules/table_updater_view.py +34 -0
  150. evileye/visualization_modules/video_thread.py +173 -0
  151. evileye/visualization_modules/visualizer.py +187 -0
  152. evileye/visualization_modules/zone_window.py +364 -0
  153. evileye-0.0.4.dist-info/METADATA +924 -0
  154. evileye-0.0.4.dist-info/RECORD +158 -0
  155. evileye-0.0.4.dist-info/WHEEL +5 -0
  156. evileye-0.0.4.dist-info/entry_points.txt +6 -0
  157. evileye-0.0.4.dist-info/licenses/LICENSE +21 -0
  158. evileye-0.0.4.dist-info/top_level.txt +1 -0
evileye/__init__.py ADDED
@@ -0,0 +1,88 @@
1
+ """
2
+ EvilEye - Intelligence video surveillance system
3
+
4
+ A comprehensive video surveillance system with object detection, tracking,
5
+ and multi-camera support.
6
+ """
7
+
8
+ __version__ = "1.0.0"
9
+ __author__ = "EvilEye Team"
10
+ __email__ = "team@evileye.com"
11
+
12
+ # Import core components
13
+ from .core import PipelineProcessors, ProcessorBase, ProcessorSource, ProcessorFrame, ProcessorStep
14
+ from .pipelines import PipelineSurveillance
15
+
16
+ # Import registered classes
17
+ from .capture import video_capture
18
+ from .object_detector import object_detection_yolo
19
+ from .object_tracker import object_tracking_botsort
20
+ from .object_multi_camera_tracker import ObjectMultiCameraTracking
21
+
22
+ # Import main modules
23
+ from . import process
24
+ from . import configure
25
+ from . import controller
26
+
27
+ # Define public API
28
+ __all__ = [
29
+ # Core components
30
+ "Pipeline",
31
+ "ProcessorBase",
32
+ "ProcessorSource",
33
+ "ProcessorFrame",
34
+ "ProcessorStep",
35
+ "PipelineSurveillance",
36
+
37
+ # Registered classes
38
+ "video_capture",
39
+ "object_detection_yolo",
40
+ "object_tracking_botsort",
41
+ "ObjectMultiCameraTracking",
42
+
43
+ # Main modules
44
+ "process",
45
+ "configure",
46
+ "controller",
47
+ ]
48
+
49
+ # Auto-fix entry points on first import
50
+ def _auto_fix_entry_points():
51
+ """Automatically fix entry points when package is imported"""
52
+ import os
53
+ import subprocess
54
+ from pathlib import Path
55
+
56
+ # Only run once per session
57
+ if hasattr(_auto_fix_entry_points, '_run'):
58
+ return
59
+ _auto_fix_entry_points._run = True
60
+
61
+ try:
62
+ # Get the project root directory
63
+ package_dir = Path(__file__).parent
64
+ project_root = package_dir.parent
65
+ fix_script = project_root / "fix_entry_points.sh"
66
+
67
+ if fix_script.exists() and os.access(fix_script, os.X_OK):
68
+ # Run the fix script silently
69
+ result = subprocess.run(
70
+ [str(fix_script)],
71
+ cwd=project_root,
72
+ capture_output=True,
73
+ text=True,
74
+ timeout=30 # 30 second timeout
75
+ )
76
+
77
+ if result.returncode == 0:
78
+ print("✅ EvilEye entry points automatically fixed")
79
+ else:
80
+ print(f"⚠️ Warning: Could not auto-fix entry points: {result.stderr}")
81
+
82
+ except Exception as e:
83
+ # Don't fail the import if fixing fails
84
+ pass
85
+
86
+ # Call auto-fix function (only if not imported as module)
87
+ if __name__ != "__main__":
88
+ _auto_fix_entry_points()
@@ -0,0 +1,4 @@
1
+ from .video_capture_base import VideoCaptureBase
2
+ from .video_capture import VideoCapture
3
+ from .video_capture import CaptureImage
4
+ from .video_capture import CaptureDeviceType
@@ -0,0 +1,229 @@
1
+ import datetime
2
+
3
+ import cv2
4
+ from threading import Lock
5
+ import time
6
+ from timeit import default_timer as timer
7
+ from .video_capture_base import VideoCaptureBase, CaptureImage, CaptureDeviceType
8
+ from enum import IntEnum
9
+
10
+ from ..core.base_class import EvilEyeBase
11
+
12
+
13
+ @EvilEyeBase.register("VideoCapture")
14
+ class VideoCapture(VideoCaptureBase):
15
+ class VideoCaptureAPIs(IntEnum):
16
+ CAP_ANY = 0
17
+ CAP_GSTREAMER = 1800
18
+ CAP_FFMPEG = 1900
19
+ CAP_IMAGES = 2000
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ self.capture = cv2.VideoCapture()
25
+ self.mutex = Lock()
26
+
27
+ def is_opened(self):
28
+ return self.capture.isOpened()
29
+
30
+ def set_params_impl(self):
31
+ super().set_params_impl()
32
+
33
+ def init_impl(self):
34
+ api_pref = self.params.get('apiPreference','CAP_FFMPEG')
35
+ if self.source_type == CaptureDeviceType.IpCamera and api_pref == "CAP_GSTREAMER": # Приведение rtsp ссылки к формату gstreamer
36
+ if '!' not in self.source_address:
37
+ str_h265 = (' ! rtph265depay ! h265parse ! avdec_h265 ! decodebin ! videoconvert ! ' # Указание кодеков и форматов
38
+ 'video/x-raw, format=(string)BGR ! appsink')
39
+ str_h264 = (' ! rtph264depay ! h264parse ! avdec_h264 ! decodebin ! videoconvert ! '
40
+ 'video/x-raw, format=(string)BGR ! appsink')
41
+
42
+ if self.source_address.find('tcp') == 0: # Задание протокола
43
+ str1 = 'rtspsrc protocols=' + 'tcp ' + 'location='
44
+ elif self.source_address.find('udp') == 0:
45
+ str1 = 'rtspsrc protocols=' + 'udp ' + 'location='
46
+ else:
47
+ str1 = 'rtspsrc protocols=' + 'tcp ' + 'location='
48
+
49
+ pos = self.source_address.find('rtsp')
50
+ source = str1 + self.source_address[pos:] + str_h265
51
+ self.capture.open(source, VideoCapture.VideoCaptureAPIs[api_pref])
52
+ if not self.is_opened(): # Если h265 не подойдет, используем h264
53
+ source = str1 + self.source_address + str_h264
54
+ self.capture.open(source, VideoCapture.VideoCaptureAPIs[api_pref])
55
+ else:
56
+ self.capture.open(self.source_address, VideoCapture.VideoCaptureAPIs[api_pref])
57
+ else:
58
+ self.capture.open(self.source_address, VideoCapture.VideoCaptureAPIs[api_pref])
59
+
60
+ self.source_fps = None
61
+ if self.capture.isOpened():
62
+ self.is_working = True
63
+ if self.source_type == CaptureDeviceType.VideoFile:
64
+ self.video_length = self.capture.get(cv2.CAP_PROP_FRAME_COUNT)
65
+ self.video_current_frame = 0
66
+ self.video_current_position = 0.0
67
+ self.finished = False
68
+ try:
69
+ self.source_fps = self.capture.get(cv2.CAP_PROP_FPS)
70
+ if self.source_fps == 0.0:
71
+ self.source_fps = None
72
+ self.video_duration = None
73
+ print(f'FPS: {self.source_fps}')
74
+
75
+ if self.source_fps is not None and self.source_type == CaptureDeviceType.VideoFile:
76
+ self.video_duration = self.video_length * 1000.0 / self.source_fps
77
+ except cv2.error as e:
78
+ print(f"Failed to read source_fps: {e} for sources {self.source_names}")
79
+ else:
80
+ print(f"Could not connect to a sources: {self.source_names}")
81
+ self.video_duration = None
82
+ self.video_length = None
83
+ self.video_current_frame = None
84
+ self.video_current_position = None
85
+ return False
86
+
87
+ return True
88
+
89
+ def release_impl(self):
90
+ self.capture.release()
91
+
92
+ def reset_impl(self):
93
+ self.release()
94
+ self.init()
95
+ timestamp = datetime.datetime.now()
96
+ if self.get_init_flag() and self.is_opened():
97
+ print(f"Reconnected to a sources: {self.source_names}")
98
+ self.is_working = True
99
+ self.reconnects.append((self.params['camera'], timestamp, self.is_working))
100
+ else:
101
+ print(f"Could not connect to sources: {self.source_names}")
102
+ for sub in self.subscribers:
103
+ sub.update()
104
+
105
+ def _grab_frames(self):
106
+ while self.run_flag:
107
+ begin_it = timer()
108
+ if not self.is_inited or self.capture is None:
109
+ time.sleep(0.1)
110
+ if self.init():
111
+ timestamp = datetime.datetime.now()
112
+ print(f"Reconnected to a sources: {self.source_names}")
113
+ self.reconnects.append((self.params['camera'], timestamp, self.is_working))
114
+ for sub in self.subscribers:
115
+ sub.update()
116
+ else:
117
+ continue
118
+
119
+ if not self.is_opened():
120
+ time.sleep(0.1)
121
+ self.reset()
122
+
123
+ is_grabbed = False
124
+ with self.mutex:
125
+ is_grabbed = self.capture.grab()
126
+ if not is_grabbed:
127
+ if self.source_type != CaptureDeviceType.VideoFile or self.loop_play:
128
+ self.is_working = False
129
+ timestamp = datetime.datetime.now()
130
+ self.disconnects.append((self.params['camera'], timestamp, self.is_working))
131
+ for sub in self.subscribers:
132
+ sub.update()
133
+ self.reset()
134
+ else:
135
+ self.finished = True
136
+
137
+ end_it = timer()
138
+ elapsed_seconds = end_it - begin_it
139
+ if self.source_fps:
140
+ fps_multiplier = 1.5 if self.source_type == CaptureDeviceType.IpCamera else 1.0
141
+ sleep_seconds = 1. / (fps_multiplier * self.source_fps) - elapsed_seconds
142
+ if sleep_seconds <= 0.0:
143
+ sleep_seconds = 0.001
144
+ else:
145
+ sleep_seconds = 0.03
146
+ time.sleep(sleep_seconds)
147
+
148
+ def _retrieve_frames(self):
149
+ while self.run_flag:
150
+ begin_it = timer()
151
+ is_read, src_image = None, None
152
+ with self.mutex:
153
+ is_read, src_image = self.capture.retrieve()
154
+ if is_read:
155
+ if self.frames_queue.full():
156
+ self.frames_queue.get()
157
+ if self.source_type == CaptureDeviceType.VideoFile:
158
+ self.video_current_frame += 1
159
+ if self.source_fps and self.source_fps > 0.0:
160
+ self.video_current_position = (self.video_current_frame * 1000.0) / self.source_fps
161
+ if self.source_type == CaptureDeviceType.IpCamera:
162
+ self.last_frame_time = datetime.datetime.now()
163
+ self.frames_queue.put([is_read, src_image, self.frame_id_counter, self.video_current_frame, self.video_current_position])
164
+ self.frame_id_counter += 1
165
+
166
+ end_it = timer()
167
+ elapsed_seconds = end_it - begin_it
168
+
169
+ retrieve_fps = self.desired_fps if self.desired_fps else self.source_fps if self.source_fps else 15
170
+ sleep_seconds = 1. / retrieve_fps - elapsed_seconds
171
+ if sleep_seconds <= 0.0:
172
+ sleep_seconds = 0.001
173
+
174
+ time.sleep(sleep_seconds)
175
+
176
+ if not self.run_flag:
177
+ print('Not run flag')
178
+ while not self.frames_queue.empty:
179
+ self.frames_queue.get()
180
+
181
+ if not self.run_flag:
182
+ print('Not run flag')
183
+ while not self.frames_queue.empty:
184
+ self.frames_queue.get()
185
+
186
+ def get_frames_impl(self) -> list[CaptureImage]:
187
+ captured_images: list[CaptureImage] = []
188
+ if self.frames_queue.empty():
189
+ return captured_images
190
+ ret, src_image, frame_id, current_video_frame, current_video_position = self.frames_queue.get()
191
+ if ret:
192
+ if self.split_stream: # Если сплит, то возвращаем список с частями потока, иначе - исходное изображение
193
+ for stream_cnt in range(self.num_split):
194
+ capture_image = CaptureImage()
195
+ capture_image.source_id = self.source_ids[stream_cnt]
196
+ capture_image.time_stamp = time.time()
197
+ capture_image.frame_id = frame_id
198
+ capture_image.current_video_frame = current_video_frame
199
+ capture_image.current_video_position = current_video_position
200
+ capture_image.image = src_image[self.src_coords[stream_cnt][1]:self.src_coords[stream_cnt][1] + int(self.src_coords[stream_cnt][3]),
201
+ self.src_coords[stream_cnt][0]:self.src_coords[stream_cnt][0] + int(self.src_coords[stream_cnt][2])].copy()
202
+ captured_images.append(capture_image)
203
+ else:
204
+ capture_image = CaptureImage()
205
+ capture_image.source_id = self.source_ids[0]
206
+ capture_image.time_stamp = time.time()
207
+ capture_image.frame_id = frame_id
208
+ capture_image.current_video_frame = current_video_frame
209
+ capture_image.current_video_position = current_video_position
210
+ capture_image.image = src_image
211
+ captured_images.append(capture_image)
212
+ return captured_images
213
+
214
+ def default(self):
215
+ pass
216
+
217
+ def test_disconnect(self):
218
+ with self.conn_mutex:
219
+ timestamp = datetime.datetime.now()
220
+ print(f'Disconnect: {timestamp}')
221
+ is_working = False
222
+ self.disconnects.append((self.source_address, timestamp, is_working))
223
+
224
+ def test_reconnect(self):
225
+ with self.conn_mutex:
226
+ timestamp = datetime.datetime.now()
227
+ print(f'Reconnect: {timestamp}')
228
+ is_working = True
229
+ self.reconnects.append((self.source_address, timestamp, is_working))
@@ -0,0 +1,181 @@
1
+ import copy
2
+ import datetime
3
+ from abc import ABC, abstractmethod
4
+ import threading
5
+ from queue import Queue
6
+ from enum import Enum
7
+ from urllib.parse import urlparse
8
+ from threading import Lock
9
+ from collections import deque
10
+ from ..core.base_class import EvilEyeBase
11
+ from ..core.frame import CaptureImage, Frame
12
+
13
+
14
+ class CaptureDeviceType(Enum):
15
+ VideoFile = "VideoFile"
16
+ IpCamera = "IpCamera"
17
+ Device = "Device"
18
+ NotSet = "NotSet"
19
+
20
+ class VideoCaptureBase(EvilEyeBase):
21
+ def __init__(self):
22
+ super().__init__()
23
+ self.source_address = None
24
+ self.username = None
25
+ self.password = None
26
+ self.pure_url = None
27
+ self.run_flag = False
28
+ self.frames_queue = Queue(maxsize=2)
29
+ self.frame_id_counter = 0
30
+ self.source_type = CaptureDeviceType.NotSet
31
+ self.source_fps = None
32
+ self.desired_fps = None
33
+ self.split_stream = False
34
+ self.num_split = 0
35
+ self.src_coords = None
36
+ self.source_ids = None
37
+ self.source_names = None
38
+ self.finished = False
39
+ self.loop_play = True
40
+ self.video_duration = None
41
+ self.video_length = None
42
+ self.video_current_frame = None
43
+ self.video_current_position = None
44
+ self.is_working = False
45
+ self.conn_mutex = Lock()
46
+ self.disconnects = []
47
+ self.reconnects = []
48
+ self.subscribers = []
49
+
50
+ self.capture_thread = None
51
+ self.grab_thread = None
52
+ self.retrieve_thread = None
53
+
54
+ def is_opened(self) -> bool:
55
+ return False
56
+
57
+ def is_working(self) -> bool:
58
+ return self.is_working
59
+
60
+ def is_finished(self) -> bool:
61
+ return self.finished
62
+
63
+ def is_running(self):
64
+ return self.run_flag
65
+
66
+ def get(self) -> list[CaptureImage]:
67
+ captured_images: list[CaptureImage] = []
68
+ if self.get_init_flag():
69
+ captured_images = self.get_frames_impl()
70
+ return captured_images
71
+
72
+ def start(self):
73
+ if not self.is_inited:
74
+ return
75
+ self.run_flag = True
76
+ # self.capture_thread = threading.Thread(target=self._capture_frames)
77
+ # self.capture_thread.start()
78
+ self.grab_thread = threading.Thread(target=self._grab_frames)
79
+ self.retrieve_thread = threading.Thread(target=self._retrieve_frames)
80
+ self.grab_thread.start()
81
+ self.retrieve_thread.start()
82
+
83
+ def stop(self):
84
+ self.run_flag = False
85
+ # if self.capture_thread:
86
+ # self.capture_thread.join()
87
+ # self.capture_thread = None
88
+ # print('Capture stopped')
89
+ if self.grab_thread:
90
+ if self.grab_thread.is_alive():
91
+ self.grab_thread.join()
92
+ self.grab_thread = None
93
+ if self.retrieve_thread:
94
+ if self.retrieve_thread.is_alive():
95
+ self.retrieve_thread.join()
96
+ self.retrieve_thread = None
97
+
98
+ def set_params_impl(self):
99
+ self.release()
100
+ self.split_stream = self.params.get('split', False)
101
+ self.num_split = self.params.get('num_split', None)
102
+ self.src_coords = self.params.get('src_coords', None)
103
+ self.source_ids = self.params.get('source_ids', None)
104
+ self.desired_fps = self.params.get('desired_fps', None)
105
+ self.source_names = self.params.get('source_names', self.source_ids)
106
+ self.loop_play = self.params.get('loop_play', True)
107
+ source_param = self.params.get('source', "")
108
+ if source_param:
109
+ self.source_type = CaptureDeviceType[source_param]
110
+ else:
111
+ self.source_type = CaptureDeviceType.NotSet
112
+ self.source_address = self.params.get('camera', '')
113
+ if self.source_type == CaptureDeviceType.IpCamera:
114
+ parsed = urlparse(self.source_address)
115
+ self.username = parsed.username
116
+ self.password = parsed.password
117
+ replaced_url = parsed._replace(netloc=f"{parsed.hostname}")
118
+ self.pure_url = replaced_url.geturl()
119
+ self.username = self.params.get('username', self.username)
120
+ self.password = self.params.get('password', self.password)
121
+ self.source_address = self.reconstruct_url(replaced_url, self.username, self.password)
122
+ else:
123
+ self.username = None
124
+ self.password = None
125
+ self.pure_url = None
126
+
127
+ def get_params_impl(self):
128
+ params = dict()
129
+ params['split'] = self.split_stream
130
+ params['num_split'] = self.num_split
131
+ params['src_coords'] = self.src_coords
132
+ params['source_ids'] = self.source_ids
133
+ params['desired_fps'] = self.desired_fps
134
+ params['source_names'] = self.source_names
135
+ params['loop_play'] = self.loop_play
136
+ params['source'] = self.source_type.name
137
+ params['camera'] = self.source_address
138
+ return params
139
+
140
+ def get_disconnects_info(self) -> list[tuple[str, datetime.datetime, bool]]:
141
+ disconnects = copy.deepcopy(self.disconnects)
142
+ self.disconnects = []
143
+ return disconnects
144
+
145
+ def get_reconnects_info(self) -> list[tuple[str, datetime.datetime, bool]]:
146
+ reconnects = copy.deepcopy(self.reconnects)
147
+ self.reconnects = []
148
+ return reconnects
149
+
150
+ @staticmethod
151
+ def reconstruct_url(url_parsed_info, username, password):
152
+ processed_username = username if (username and username != "") else None
153
+ processed_password = password if (password and password != "") else None
154
+ if not processed_password and not processed_username:
155
+ return url_parsed_info.geturl()
156
+
157
+ if not processed_password:
158
+ reconstructed_url = url_parsed_info._replace(netloc=f"{processed_username}@{url_parsed_info.hostname}")
159
+ return reconstructed_url.geturl()
160
+
161
+ reconstructed_url = url_parsed_info._replace(netloc=f"{processed_username}:{processed_password}@{url_parsed_info.hostname}")
162
+ return reconstructed_url.geturl()
163
+
164
+ def subscribe(self, *subscribers):
165
+ self.subscribers = list(subscribers)
166
+
167
+ # @abstractmethod
168
+ # def _capture_frames(self):
169
+ # pass
170
+
171
+ @abstractmethod
172
+ def get_frames_impl(self) -> list[CaptureImage]:
173
+ pass
174
+
175
+ @abstractmethod
176
+ def _grab_frames(self):
177
+ pass
178
+
179
+ @abstractmethod
180
+ def _retrieve_frames(self):
181
+ pass