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.
- evileye/__init__.py +88 -0
- evileye/capture/__init__.py +4 -0
- evileye/capture/video_capture.py +229 -0
- evileye/capture/video_capture_base.py +181 -0
- evileye/cli.py +494 -0
- evileye/cli_wrapper.py +24 -0
- evileye/configure.py +36 -0
- evileye/configure_wrapper.py +17 -0
- evileye/controller/__init__.py +2 -0
- evileye/controller/controller.py +844 -0
- evileye/core/__init__.py +12 -0
- evileye/core/base_class.py +105 -0
- evileye/core/frame.py +12 -0
- evileye/core/mp_control.py +39 -0
- evileye/core/mp_worker.py +41 -0
- evileye/core/pipeline_base.py +347 -0
- evileye/core/pipeline_processors.py +170 -0
- evileye/core/pipeline_simple.py +103 -0
- evileye/core/processor_base.py +84 -0
- evileye/core/processor_frame.py +30 -0
- evileye/core/processor_source.py +32 -0
- evileye/core/processor_step.py +51 -0
- evileye/create.py +195 -0
- evileye/create_wrapper.py +22 -0
- evileye/credentials_proto.json +17 -0
- evileye/database_config.json +110 -0
- evileye/database_controller/__init__.py +2 -0
- evileye/database_controller/database_controller_base.py +76 -0
- evileye/database_controller/database_controller_pg.py +467 -0
- evileye/database_controller/db_adapter.py +79 -0
- evileye/database_controller/db_adapter_cam_events.py +59 -0
- evileye/database_controller/db_adapter_fov_events.py +135 -0
- evileye/database_controller/db_adapter_objects.py +181 -0
- evileye/database_controller/db_adapter_zone_events.py +153 -0
- evileye/events_control/events_controller.py +77 -0
- evileye/events_control/events_processor.py +159 -0
- evileye/events_detectors/cam_events_detector.py +68 -0
- evileye/events_detectors/event.py +28 -0
- evileye/events_detectors/event_cameras.py +18 -0
- evileye/events_detectors/event_fov.py +23 -0
- evileye/events_detectors/event_zone.py +43 -0
- evileye/events_detectors/events_detector.py +42 -0
- evileye/events_detectors/fov_events_detector.py +160 -0
- evileye/events_detectors/zone.py +40 -0
- evileye/events_detectors/zone_events_detector.py +327 -0
- evileye/icons/add_zone.svg +17 -0
- evileye/icons/cam_history.svg +4 -0
- evileye/icons/delete_zone.svg +2 -0
- evileye/icons/display_zones.svg +2 -0
- evileye/icons/journal.svg +6 -0
- evileye/icons/run_app.svg +5 -0
- evileye/icons/save_icon.svg +4 -0
- evileye/icons/zone_polygon.svg +2 -0
- evileye/icons/zone_rect.svg +2 -0
- evileye/launch.py +495 -0
- evileye/launch_wrapper.py +17 -0
- evileye/models/yolov8n.pt +0 -0
- evileye/object_detector/__init__.py +8 -0
- evileye/object_detector/background_subtraction_base.py +14 -0
- evileye/object_detector/background_subtraction_gmm.py +48 -0
- evileye/object_detector/detection_thread_base.py +138 -0
- evileye/object_detector/detection_thread_yolo.py +51 -0
- evileye/object_detector/detection_thread_yolo_mp.py +53 -0
- evileye/object_detector/mp_worker_yolo.py +33 -0
- evileye/object_detector/object_detection_base.py +141 -0
- evileye/object_detector/object_detection_yolo.py +53 -0
- evileye/object_detector/object_detection_yolo_mp.py +47 -0
- evileye/object_multi_camera_tracker/__init__.py +6 -0
- evileye/object_multi_camera_tracker/custom_object_tracking.py +464 -0
- evileye/object_multi_camera_tracker/mctrack.py +118 -0
- evileye/object_multi_camera_tracker/object_multicam_tracking_base.py +66 -0
- evileye/object_tracker/__init__.py +3 -0
- evileye/object_tracker/object_tracking_base.py +79 -0
- evileye/object_tracker/object_tracking_botsort.py +183 -0
- evileye/object_tracker/object_tracking_impl.py +24 -0
- evileye/object_tracker/trackers/__init__.py +7 -0
- evileye/object_tracker/trackers/basetrack.py +105 -0
- evileye/object_tracker/trackers/bot_sort.py +267 -0
- evileye/object_tracker/trackers/byte_tracker.py +460 -0
- evileye/object_tracker/trackers/cfg/utils.py +14 -0
- evileye/object_tracker/trackers/encoder.py +11 -0
- evileye/object_tracker/trackers/onnx_encoder.py +97 -0
- evileye/object_tracker/trackers/sctrack.py +114 -0
- evileye/object_tracker/trackers/track.py +89 -0
- evileye/object_tracker/trackers/track_encoder.py +8 -0
- evileye/object_tracker/trackers/tracker_test.py +54 -0
- evileye/object_tracker/trackers/tracking_wrapper.py +46 -0
- evileye/object_tracker/trackers/utils/__init__.py +1 -0
- evileye/object_tracker/trackers/utils/gmc.py +367 -0
- evileye/object_tracker/trackers/utils/kalman_filter.py +360 -0
- evileye/object_tracker/trackers/utils/matching.py +138 -0
- evileye/object_tracker/tracking_results.py +44 -0
- evileye/objects_handler/labeling_manager.py +663 -0
- evileye/objects_handler/object_result.py +77 -0
- evileye/objects_handler/objects_handler.py +485 -0
- evileye/pipelines/__init__.py +4 -0
- evileye/pipelines/pipeline_capture.py +216 -0
- evileye/pipelines/pipeline_surveillance.py +167 -0
- evileye/preprocessing/__init__.py +4 -0
- evileye/preprocessing/preprocessing_base.py +70 -0
- evileye/preprocessing/preprocessing_factory.py +25 -0
- evileye/preprocessing/preprocessing_pipeline.py +50 -0
- evileye/preprocessing/steps/__init__.py +6 -0
- evileye/preprocessing/steps/step_abstract.py +24 -0
- evileye/preprocessing/steps/step_clahe.py +21 -0
- evileye/preprocessing/steps/step_inpaint.py +24 -0
- evileye/preprocessing/steps/step_input.py +12 -0
- evileye/preprocessing/steps/step_normalize.py +13 -0
- evileye/preprocessing/steps/step_output.py +12 -0
- evileye/process.py +123 -0
- evileye/process_wrapper.py +19 -0
- evileye/samples_configs/multi_videos.json +147 -0
- evileye/samples_configs/pipeline_capture.json +80 -0
- evileye/samples_configs/single_ip_camera.json +114 -0
- evileye/samples_configs/single_video.json +110 -0
- evileye/samples_configs/single_video_split.json +213 -0
- evileye/samples_configs/single_video_with_detplanes.json +148 -0
- evileye/utils/__init__.py +0 -0
- evileye/utils/download_samples.py +167 -0
- evileye/utils/threading_events.py +20 -0
- evileye/utils/utils.py +739 -0
- evileye/visualization_modules/__init__.py +0 -0
- evileye/visualization_modules/configurer/configurer_tabs/database_tab.py +137 -0
- evileye/visualization_modules/configurer/configurer_tabs/detector_tab.py +124 -0
- evileye/visualization_modules/configurer/configurer_tabs/detector_widget.py +141 -0
- evileye/visualization_modules/configurer/configurer_tabs/events_tab.py +184 -0
- evileye/visualization_modules/configurer/configurer_tabs/handler_tab.py +99 -0
- evileye/visualization_modules/configurer/configurer_tabs/src_tab.py +167 -0
- evileye/visualization_modules/configurer/configurer_tabs/src_widget.py +378 -0
- evileye/visualization_modules/configurer/configurer_tabs/tracker_tab.py +135 -0
- evileye/visualization_modules/configurer/configurer_tabs/tracker_widget.py +208 -0
- evileye/visualization_modules/configurer/configurer_tabs/visualizer_tab.py +145 -0
- evileye/visualization_modules/configurer/configurer_window.py +349 -0
- evileye/visualization_modules/configurer/db_connection_window.py +100 -0
- evileye/visualization_modules/configurer/jobs_history_journal.py +263 -0
- evileye/visualization_modules/configurer/parameters_processing.py +54 -0
- evileye/visualization_modules/db_journal.py +115 -0
- evileye/visualization_modules/events_journal.py +487 -0
- evileye/visualization_modules/events_journal_json.py +546 -0
- evileye/visualization_modules/handler_journal_view.py +447 -0
- evileye/visualization_modules/journal_adapters/jadapter_base.py +48 -0
- evileye/visualization_modules/journal_adapters/jadapter_cam_events.py +28 -0
- evileye/visualization_modules/journal_adapters/jadapter_fov_events.py +27 -0
- evileye/visualization_modules/journal_adapters/jadapter_zone_events.py +17 -0
- evileye/visualization_modules/journal_data_source.py +33 -0
- evileye/visualization_modules/journal_data_source_json.py +200 -0
- evileye/visualization_modules/main_window.py +378 -0
- evileye/visualization_modules/table_data_thread.py +61 -0
- evileye/visualization_modules/table_updater_view.py +34 -0
- evileye/visualization_modules/video_thread.py +173 -0
- evileye/visualization_modules/visualizer.py +187 -0
- evileye/visualization_modules/zone_window.py +364 -0
- evileye-0.0.4.dist-info/METADATA +924 -0
- evileye-0.0.4.dist-info/RECORD +158 -0
- evileye-0.0.4.dist-info/WHEEL +5 -0
- evileye-0.0.4.dist-info/entry_points.txt +6 -0
- evileye-0.0.4.dist-info/licenses/LICENSE +21 -0
- 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,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
|