sciveo 0.1.66__tar.gz → 0.1.68__tar.gz

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 (169) hide show
  1. {sciveo-0.1.66 → sciveo-0.1.68}/PKG-INFO +1 -1
  2. sciveo-0.1.68/sciveo/media/capture/motion_detection.py +139 -0
  3. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/capture/nvr.py +19 -7
  4. sciveo-0.1.68/sciveo/media/capture/readers.py +122 -0
  5. sciveo-0.1.68/sciveo/tools/totp.py +80 -0
  6. sciveo-0.1.68/sciveo/version.py +2 -0
  7. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/PKG-INFO +1 -1
  8. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/SOURCES.txt +5 -1
  9. sciveo-0.1.68/test/test_totp.py +41 -0
  10. sciveo-0.1.66/sciveo/version.py +0 -2
  11. {sciveo-0.1.66 → sciveo-0.1.68}/README.md +0 -0
  12. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/__init__.py +0 -0
  13. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/api/__init__.py +0 -0
  14. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/api/base.py +0 -0
  15. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/api/predictors.py +0 -0
  16. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/api/server.py +0 -0
  17. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/api/upload.py +0 -0
  18. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/cli.py +0 -0
  19. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/common/__init__.py +0 -0
  20. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/common/configuration.py +0 -0
  21. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/common/model.py +0 -0
  22. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/common/optimizers.py +0 -0
  23. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/common/sampling.py +0 -0
  24. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/content/__init__.py +0 -0
  25. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/content/dataset.py +0 -0
  26. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/content/experiment.py +0 -0
  27. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/content/project.py +0 -0
  28. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/content/runner.py +0 -0
  29. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/__init__.py +0 -0
  30. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/capture/__init__.py +0 -0
  31. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/capture/cam.py +0 -0
  32. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/__init__.py +0 -0
  33. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/base.py +0 -0
  34. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/encoders/__init__.py +0 -0
  35. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/encoders/base.py +0 -0
  36. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/encoders/normalizer.py +0 -0
  37. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/nlp/__init__.py +0 -0
  38. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/nlp/search.py +0 -0
  39. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/time_series/__init__.py +0 -0
  40. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/time_series/dataset.py +0 -0
  41. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/time_series/predictor.py +0 -0
  42. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/time_series/trainer.py +0 -0
  43. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/ml/time_series/window_generator.py +0 -0
  44. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/__init__.py +0 -0
  45. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/base.py +0 -0
  46. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/job_daemon.py +0 -0
  47. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/layouts/__init__.py +0 -0
  48. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/layouts/base.py +0 -0
  49. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/pipeline.py +0 -0
  50. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/postprocessors/__init__.py +0 -0
  51. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/postprocessors/base.py +0 -0
  52. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/postprocessors/default.py +0 -0
  53. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/__init__.py +0 -0
  54. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/audio/__init__.py +0 -0
  55. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/audio/audio.py +0 -0
  56. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/audio/audio_extractor_process.py +0 -0
  57. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/aws.py +0 -0
  58. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/base.py +0 -0
  59. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/file/__init__.py +0 -0
  60. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/file/archive.py +0 -0
  61. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/__init__.py +0 -0
  62. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/album.py +0 -0
  63. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/album_in_image.py +0 -0
  64. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/depth_esimation.py +0 -0
  65. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/embeddings.py +0 -0
  66. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/filters.py +0 -0
  67. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/generators.py +0 -0
  68. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/histogram.py +0 -0
  69. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/mask.py +0 -0
  70. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/object_detection.py +0 -0
  71. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/resize.py +0 -0
  72. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/segmentation.py +0 -0
  73. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/image/watermark.py +0 -0
  74. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/media_info.py +0 -0
  75. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/nlp/__init__.py +0 -0
  76. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/nlp/address.py +0 -0
  77. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/qr.py +0 -0
  78. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/__init__.py +0 -0
  79. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/base.py +0 -0
  80. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/dataset.py +0 -0
  81. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/time_series/__init__.py +0 -0
  82. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/time_series/predictor.py +0 -0
  83. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/sci/time_series/trainer.py +0 -0
  84. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/tpu_base.py +0 -0
  85. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/__init__.py +0 -0
  86. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/generators.py +0 -0
  87. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/motion_detection.py +0 -0
  88. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/resize.py +0 -0
  89. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/video_album.py +0 -0
  90. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/video_frames.py +0 -0
  91. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/processors/video/video_resample.py +0 -0
  92. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/queues.py +0 -0
  93. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/server.py +0 -0
  94. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/web/__init__.py +0 -0
  95. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/pipelines/web/server.py +0 -0
  96. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/tools/__init__.py +0 -0
  97. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/media/tools/video_interactive.py +0 -0
  98. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/__init__.py +0 -0
  99. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/base.py +0 -0
  100. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/dataset/__init__.py +0 -0
  101. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/dataset/object_detection.py +0 -0
  102. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/evaluation/__init__.py +0 -0
  103. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/evaluation/markdown.py +0 -0
  104. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/evaluation/object_detection.py +0 -0
  105. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/__init__.py +0 -0
  106. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/base.py +0 -0
  107. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/description.py +0 -0
  108. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/embeddings.py +0 -0
  109. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/object_detection.py +0 -0
  110. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/tools.py +0 -0
  111. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/images/transformers.py +0 -0
  112. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/nlp/__init__.py +0 -0
  113. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/nlp/embeddings.py +0 -0
  114. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/nlp/tokenizers/__init__.py +0 -0
  115. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/nlp/tokenizers/bpe.py +0 -0
  116. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/video/__init__.py +0 -0
  117. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/ml/video/description.py +0 -0
  118. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/__init__.py +0 -0
  119. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/monitor.py +0 -0
  120. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/start.py +0 -0
  121. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/watchdog/__init__.py +0 -0
  122. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/watchdog/base.py +0 -0
  123. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/monitoring/watchdog/process.py +0 -0
  124. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/network/__init__.py +0 -0
  125. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/network/camera.py +0 -0
  126. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/network/sniffer.py +0 -0
  127. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/network/tools.py +0 -0
  128. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/__init__.py +0 -0
  129. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/array.py +0 -0
  130. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/aws/__init__.py +0 -0
  131. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/aws/priority_queue.py +0 -0
  132. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/aws/s3.py +0 -0
  133. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/common.py +0 -0
  134. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/complexity.py +0 -0
  135. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/compress.py +0 -0
  136. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/configuration.py +0 -0
  137. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/crypto.py +0 -0
  138. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/daemon.py +0 -0
  139. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/draw/__init__.py +0 -0
  140. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/draw/contours.py +0 -0
  141. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/formating.py +0 -0
  142. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/hardware.py +0 -0
  143. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/http.py +0 -0
  144. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/logger.py +0 -0
  145. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/os.py +0 -0
  146. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/queue.py +0 -0
  147. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/random.py +0 -0
  148. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/remote.py +0 -0
  149. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/simple_counter.py +0 -0
  150. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/synchronized.py +0 -0
  151. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/tools/timers.py +0 -0
  152. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/web/__init__.py +0 -0
  153. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo/web/common.py +0 -0
  154. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/dependency_links.txt +0 -0
  155. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/entry_points.txt +0 -0
  156. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/requires.txt +0 -0
  157. {sciveo-0.1.66 → sciveo-0.1.68}/sciveo.egg-info/top_level.txt +0 -0
  158. {sciveo-0.1.66 → sciveo-0.1.68}/setup.cfg +0 -0
  159. {sciveo-0.1.66 → sciveo-0.1.68}/setup.py +0 -0
  160. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_complexity.py +0 -0
  161. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_compress.py +0 -0
  162. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_configuration.py +0 -0
  163. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_crypto.py +0 -0
  164. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_eval_markdown.py +0 -0
  165. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_ml_datasets.py +0 -0
  166. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_monitoring.py +0 -0
  167. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_runner.py +0 -0
  168. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_sampling.py +0 -0
  169. {sciveo-0.1.66 → sciveo-0.1.68}/test/test_tokenizers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sciveo
3
- Version: 0.1.66
3
+ Version: 0.1.68
4
4
  Description-Content-Type: text/markdown
5
5
  Provides-Extra: mon
6
6
  Provides-Extra: net
@@ -0,0 +1,139 @@
1
+ #
2
+ # Stanislav Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact s.georgiev@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+ import time
13
+ import os
14
+ import shutil
15
+ import cv2
16
+ import numpy as np
17
+
18
+ from sciveo.tools.logger import *
19
+ from sciveo.tools.daemon import DaemonBase
20
+ from sciveo.media.capture.readers import VideoReaderFFMPEG
21
+ from sciveo.tools.queue import TouchedFilePathQueue
22
+
23
+
24
+ class MotionDetectorGrid:
25
+ def __init__(self, grid):
26
+ self.grid = np.array(grid)
27
+ self.grid_mask = None
28
+ self.zero = False
29
+
30
+ def init_grid(self, frame):
31
+ self.grid_mask = np.zeros_like(frame, dtype=np.float32)
32
+
33
+ for i in range(0, self.grid_mask.shape[0]):
34
+ for j in range(0, self.grid_mask.shape[1]):
35
+ gi = int(self.grid.shape[0] * i / self.grid_mask.shape[0])
36
+ gj = int(self.grid.shape[1] * j / self.grid_mask.shape[1])
37
+
38
+ self.grid_mask[i][j] = self.grid[gi][gj]
39
+
40
+ self.zero = self.grid_mask.sum() <= 0.0
41
+
42
+ def apply(self, frame):
43
+ if self.grid_mask is None:
44
+ self.init_grid(frame)
45
+ result = (frame * self.grid_mask).astype(np.uint8)
46
+ return result
47
+
48
+
49
+ class VideoMotionDetector:
50
+ def __init__(self, source_id, configuration):
51
+ super().__init__()
52
+ self.source_id = source_id
53
+ self.configuration = configuration
54
+ self.count_motion_hold = 0
55
+ self.detection_grid = MotionDetectorGrid(self.configuration["grid"])
56
+
57
+ self.init()
58
+
59
+ def init(self):
60
+ self.fgbg = cv2.createBackgroundSubtractorMOG2(history=50, varThreshold=20, detectShadows=False)
61
+
62
+ def apply(self, frame):
63
+ md_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
64
+ md_frame = cv2.blur(src=md_frame, ksize=(10,10))
65
+ md_frame = self.fgbg.apply(md_frame)
66
+ md_frame = self.detection_grid.apply(md_frame)
67
+ return md_frame
68
+
69
+ def check_motion(self, fgmask):
70
+ k_motion_level = 0.5 + 0.5 * min(self.configuration["threshold_motion_hold"], self.count_motion_hold) / self.configuration["threshold_motion_hold"]
71
+ current_threshold = self.configuration["threshold_motion"] * k_motion_level
72
+ motion_level = np.mean(fgmask)
73
+
74
+ if motion_level > current_threshold:
75
+ self.count_motion_hold += 1
76
+ if self.count_motion_hold >= self.configuration["threshold_motion_hold"]:
77
+ debug(self.source_id, "motion detected", motion_level, "threshold", current_threshold)
78
+ return True
79
+ else:
80
+ self.count_motion_hold = 0
81
+ return False
82
+
83
+ def __call__(self, list_frames):
84
+ self.count_motion_hold = 0
85
+
86
+ for frame in list_frames:
87
+ try:
88
+ if self.detection_grid.zero:
89
+ continue
90
+
91
+ fgmask = self.apply(frame)
92
+
93
+ if self.check_motion(fgmask):
94
+ return True
95
+ except Exception as e:
96
+ exception(e)
97
+ time.sleep(3)
98
+
99
+ return False
100
+
101
+
102
+ class MotionDetectorDaemon(DaemonBase):
103
+ def __init__(self, configuration):
104
+ super().__init__()
105
+ self.configuration = configuration
106
+ self.queue = TouchedFilePathQueue(self.configuration["src"], period=3, touched_timeout=3)
107
+ self.motion_detectors = {}
108
+
109
+ def on_motion(self, source_id, file_name, file_path, configuration):
110
+ debug("MOTION", source_id, file_name, configuration)
111
+ if "dst" in self.configuration:
112
+ dst_file_path = os.path.join(self.configuration["dst"], file_name)
113
+ shutil.copy(file_path, dst_file_path)
114
+ debug("CP", file_path, "=>", dst_file_path)
115
+
116
+ def run(self):
117
+ while(self.is_running):
118
+ try:
119
+ file_name, file_path = self.queue.pop()
120
+ debug("pop", file_name, file_path)
121
+
122
+ file_name_split = file_name.split("___")
123
+ if len(file_name_split) >= 3:
124
+ source_id = file_name_split[0]
125
+
126
+ if source_id in self.configuration:
127
+ configuration = self.configuration.get(source_id, { "threshold_motion": 5, "threshold_motion_hold": 8, "grid": [[0.0]] })
128
+ md = self.motion_detectors.get(source_id, VideoMotionDetector(source_id, configuration))
129
+ motion = md(VideoReaderFFMPEG.read(file_path, resolution=360, RGB=False, gpu_id=0))
130
+
131
+ if motion:
132
+ self.on_motion(source_id, file_name, file_path, configuration)
133
+
134
+ os.remove(file_path)
135
+ debug("RM", file_path)
136
+
137
+ except Exception as e:
138
+ exception(e)
139
+ time.sleep(5)
@@ -22,6 +22,7 @@ from sciveo.tools.logger import *
22
22
  from sciveo.tools.daemon import DaemonBase
23
23
  from sciveo.tools.queue import TouchedFilePathQueue
24
24
  from sciveo.tools.simple_counter import RunCounter
25
+ from sciveo.media.capture.motion_detection import MotionDetectorDaemon
25
26
 
26
27
 
27
28
  class VideoCameraCaptureDaemon(DaemonBase):
@@ -117,7 +118,7 @@ class VideoRecorder:
117
118
  def __init__(self, path_configuration):
118
119
  with open(path_configuration, 'r') as fp:
119
120
  self.configuration = json.load(fp)
120
- self.cams = []
121
+ self.daemons = []
121
122
 
122
123
  for cam_id, cam_config in self.configuration["cam"].items():
123
124
  cam = VideoCameraCaptureDaemon(
@@ -126,14 +127,17 @@ class VideoRecorder:
126
127
  self.configuration.get("max_video_len", 60),
127
128
  self.configuration.get("transport", "tcp")
128
129
  )
129
- self.cams.append(cam)
130
+ self.daemons.append(cam)
131
+
132
+ if "motion" in self.configuration:
133
+ self.daemons.append(MotionDetectorDaemon(configuration=self.configuration["motion"]))
130
134
 
131
135
  self.queue = TouchedFilePathQueue(self.configuration["path"]["tmp"], period=5, touched_timeout=5)
132
136
  self.cleaner_timer = RunCounter(1000, self.clean_old_videos)
133
137
 
134
138
  def start(self):
135
- for cam in self.cams:
136
- cam.start()
139
+ for d in self.daemons:
140
+ d.start()
137
141
 
138
142
  time.sleep(10)
139
143
 
@@ -171,9 +175,17 @@ class VideoRecorder:
171
175
  video_base_path = os.path.join(self.configuration["path"]["video"], cam_id, video_date)
172
176
  video_file_path = os.path.join(video_base_path, video_file_name)
173
177
 
174
- debug("MV", file_path, "=>", video_file_path)
175
178
  os.makedirs(video_base_path, exist_ok=True)
176
- shutil.move(file_path, video_file_path)
179
+ shutil.copy(file_path, video_file_path)
180
+ debug("CP", file_path, "=>", video_file_path)
181
+
182
+ if "motion" in self.configuration:
183
+ motion_file_path = os.path.join(self.configuration["motion"]["src"], f"{cam_id}___{video_date}___{video_file_name}")
184
+ shutil.move(file_path, motion_file_path)
185
+ debug("MV", file_path, "=>", motion_file_path)
186
+ else:
187
+ os.remove(file_path)
188
+ debug("RM", file_path)
177
189
 
178
190
  def clean_old_videos(self):
179
191
  try:
@@ -182,7 +194,7 @@ class VideoRecorder:
182
194
  debug("cmd", cmd)
183
195
  os.system(cmd)
184
196
  except Exception as e:
185
- excprint([self], e, cmd)
197
+ exception(e, cmd)
186
198
 
187
199
 
188
200
  if __name__ == '__main__':
@@ -0,0 +1,122 @@
1
+ #
2
+ # Stanislav Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact s.georgiev@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+ import os
13
+ import select
14
+ import json
15
+ import subprocess
16
+ import ffmpeg
17
+ import json
18
+ import numpy as np
19
+ from sciveo.tools.logger import *
20
+ from sciveo.tools.timers import Timer
21
+
22
+
23
+ class VideoReaderFFMPEG:
24
+ def __init__(self, path, resolution=720, RGB=False, gpu_id=-1) -> None:
25
+ self.path = path
26
+ self.resolution = resolution
27
+ self.RGB = RGB
28
+ self.gpu_id = gpu_id
29
+
30
+ @staticmethod
31
+ def get_dim(w, h, resolution):
32
+ if resolution <= 0:
33
+ return w, h
34
+
35
+ if h > resolution:
36
+ ratio = resolution / h
37
+ else:
38
+ ratio = 1.0
39
+ width = int(w * ratio)
40
+ height = int(h * ratio)
41
+ width += width % 2
42
+ height += height % 2
43
+ return width, height
44
+
45
+ @staticmethod
46
+ def read(file_path, resolution=720, RGB=False, gpu_id=-1, timeout=30):
47
+ timer_video_read = Timer()
48
+ frames = []
49
+
50
+ probe = subprocess.check_output([
51
+ "ffprobe", "-v", "error",
52
+ "-select_streams", "v:0",
53
+ "-show_entries", "stream=width,height,codec_name",
54
+ "-of", "json", file_path
55
+ ])
56
+ stream_data = json.loads(probe)["streams"][0]
57
+
58
+ in_w, in_h = int(stream_data["width"]), int(stream_data["height"])
59
+ out_w, out_h = VideoReaderFFMPEG.get_dim(in_w, in_h, resolution)
60
+ frame_size = out_w * out_h * 3
61
+
62
+ gpu_nvdec_id = int(os.environ.get("GPU_NVDEC_H264_ID", gpu_id))
63
+
64
+ codec = stream_data["codec_name"]
65
+ if codec in ["h264", "avc1"]:
66
+ decoder = "h264_cuvid"
67
+ elif codec in ["hevc", "h265", "hvc1"]:
68
+ decoder = "hevc_cuvid"
69
+ gpu_nvdec_id = int(os.environ.get("GPU_NVDEC_H265_ID", gpu_nvdec_id))
70
+
71
+ HW = "CPU"
72
+ pix_fmt = "rgb24" if RGB else "bgr24"
73
+
74
+ if min(gpu_id, gpu_nvdec_id) < 0:
75
+ HW = "CPU"
76
+ ffmpeg_input = ffmpeg.input(file_path)
77
+ else:
78
+ HW = "GPU"
79
+ ffmpeg_input = ffmpeg.input(file_path, hwaccel='cuda', hwaccel_device=gpu_nvdec_id, c=decoder)
80
+
81
+ if in_w != out_w or in_h != out_h:
82
+ ffmpeg_input = ffmpeg_input.filter('scale', out_w, out_h)
83
+
84
+ process = (
85
+ ffmpeg_input
86
+ .output('pipe:', format='rawvideo', pix_fmt=pix_fmt, vsync='vfr')
87
+ .global_args('-v', 'quiet')
88
+ .run_async(pipe_stdout=True, pipe_stderr=subprocess.DEVNULL)
89
+ )
90
+
91
+ fd = process.stdout.fileno()
92
+
93
+ while True:
94
+ ready, _, _ = select.select([fd], [], [], timeout)
95
+ if not ready:
96
+ warning("VideoReaderFFMPEG::read", file_path, f"[{HW}][{gpu_id}] not ready FAIL")
97
+ process.kill()
98
+ process.wait()
99
+ frames = []
100
+ break
101
+
102
+ in_bytes = process.stdout.read(frame_size)
103
+ if not in_bytes:
104
+ break
105
+ frame = np.frombuffer(in_bytes, np.uint8).reshape([out_h, out_w, 3]).copy()
106
+ frames.append(frame)
107
+
108
+ process.wait()
109
+
110
+ if gpu_id >= 0 and len(frames) == 0:
111
+ warning("VideoReaderFFMPEG::read", file_path, f"[{HW}][{gpu_id}] reading not successful, fallback to CPU")
112
+ return VideoReaderFFMPEG.read(file_path, resolution, RGB, gpu_id=-1)
113
+
114
+ elapsed = timer_video_read.stop()
115
+ FPS = round(len(frames) / elapsed, 2)
116
+ debug("VideoReaderFFMPEG::read", file_path, f"[{HW}] video reading {len(frames)} frames, codec[{codec}] decoder[{decoder}] pix_fmt[{pix_fmt}] [{out_w}x{out_h}], elapsed {round(elapsed, 1)} FPS {FPS}")
117
+ return frames
118
+
119
+ def __call__(self):
120
+ return VideoReaderFFMPEG.read(self.path, self.resolution, RGB=self.RGB, gpu_id=self.gpu_id)
121
+
122
+
@@ -0,0 +1,80 @@
1
+ #
2
+ # Pavlin Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact pavlin@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+
13
+ import os
14
+ import hmac
15
+ import base64
16
+ import struct
17
+ import hashlib
18
+ import time
19
+
20
+ from sciveo.tools.logger import *
21
+
22
+
23
+ class AuthTOTP:
24
+ def __init__(self, secret: str, interval: int = 30, digits: int = 6, algo=hashlib.sha1):
25
+ """
26
+ secret: the private key
27
+ interval: time step in seconds (default 30)
28
+ digits: number of digits in code (default 6)
29
+ algo: hash function (default SHA1, per RFC 6238)
30
+ """
31
+ self.secret = secret
32
+ self.interval = interval
33
+ self.digits = digits
34
+ self.algo = algo
35
+
36
+ @staticmethod
37
+ def new_private_key(length: int = 20) -> str:
38
+ """
39
+ Generate a new Base32 secret key.
40
+ """
41
+ raw = os.urandom(length)
42
+ return base64.b32encode(raw).decode("utf-8").replace("=", "")
43
+
44
+ def _generate_otp(self, for_time: int) -> int:
45
+ """
46
+ Internal: generate OTP for given time counter.
47
+ """
48
+ pad_len = (8 - len(self.secret) % 8) % 8
49
+ secret_padded = self.secret + ("=" * pad_len)
50
+ key = base64.b32decode(secret_padded, casefold=True)
51
+
52
+ counter = struct.pack(">Q", for_time)
53
+ hmac_digest = hmac.new(key, counter, self.algo).digest()
54
+
55
+ offset = hmac_digest[-1] & 0x0F
56
+ code = struct.unpack(">I", hmac_digest[offset:offset+4])[0] & 0x7FFFFFFF
57
+ return code % (10 ** self.digits)
58
+
59
+ def next_token(self, for_time: int = None) -> str:
60
+ """
61
+ Return the TOTP token for current (or given) time.
62
+ """
63
+ if for_time is None:
64
+ for_time = int(time.time())
65
+ counter = for_time // self.interval
66
+ return str(self._generate_otp(counter)).zfill(self.digits)
67
+
68
+ def verify_token(self, token: str, for_time: int = None, window: int = 1) -> bool:
69
+ """
70
+ Verify a token, allowing ±window intervals (to handle clock skew).
71
+ """
72
+ if for_time is None:
73
+ for_time = int(time.time())
74
+ counter = for_time // self.interval
75
+ for delta in range(-window, window + 1):
76
+ candidate = str(self._generate_otp(counter + delta)).zfill(self.digits)
77
+ # debug("verify", token, "delta", delta, "candidate", candidate)
78
+ if hmac.compare_digest(candidate, token):
79
+ return True
80
+ return False
@@ -0,0 +1,2 @@
1
+
2
+ __version__ = '0.1.68'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sciveo
3
- Version: 0.1.66
3
+ Version: 0.1.68
4
4
  Description-Content-Type: text/markdown
5
5
  Provides-Extra: mon
6
6
  Provides-Extra: net
@@ -27,7 +27,9 @@ sciveo/content/runner.py
27
27
  sciveo/media/__init__.py
28
28
  sciveo/media/capture/__init__.py
29
29
  sciveo/media/capture/cam.py
30
+ sciveo/media/capture/motion_detection.py
30
31
  sciveo/media/capture/nvr.py
32
+ sciveo/media/capture/readers.py
31
33
  sciveo/media/ml/__init__.py
32
34
  sciveo/media/ml/base.py
33
35
  sciveo/media/ml/encoders/__init__.py
@@ -143,6 +145,7 @@ sciveo/tools/remote.py
143
145
  sciveo/tools/simple_counter.py
144
146
  sciveo/tools/synchronized.py
145
147
  sciveo/tools/timers.py
148
+ sciveo/tools/totp.py
146
149
  sciveo/tools/aws/__init__.py
147
150
  sciveo/tools/aws/priority_queue.py
148
151
  sciveo/tools/aws/s3.py
@@ -159,4 +162,5 @@ test/test_ml_datasets.py
159
162
  test/test_monitoring.py
160
163
  test/test_runner.py
161
164
  test/test_sampling.py
162
- test/test_tokenizers.py
165
+ test/test_tokenizers.py
166
+ test/test_totp.py
@@ -0,0 +1,41 @@
1
+ #
2
+ # Pavlin Georgiev, Softel Labs
3
+ #
4
+ # This is a proprietary file and may not be copied,
5
+ # distributed, or modified without express permission
6
+ # from the owner. For licensing inquiries, please
7
+ # contact pavlin@softel.bg.
8
+ #
9
+ # 2025
10
+ #
11
+
12
+ import time
13
+ import unittest
14
+
15
+ from sciveo.tools.totp import *
16
+
17
+
18
+ class TestTOTP(unittest.TestCase):
19
+ @classmethod
20
+ def setUpClass(cls):
21
+ cls.secret = AuthTOTP.new_private_key(length=32)
22
+ cls.auth = AuthTOTP(cls.secret, digits=8)
23
+
24
+ def test_pass(self):
25
+ token = self.auth.next_token()
26
+ self.assertTrue(self.auth.verify_token(token=token))
27
+
28
+ def test_expiry_time_fail(self):
29
+ auth = AuthTOTP(self.secret, interval=3, digits=8)
30
+ token = auth.next_token()
31
+ self.assertTrue(auth.verify_token(token=token))
32
+ time.sleep(2)
33
+ self.assertTrue(auth.verify_token(token=token))
34
+ time.sleep(2)
35
+ self.assertTrue(auth.verify_token(token=token))
36
+ time.sleep(2)
37
+ self.assertFalse(auth.verify_token(token=token))
38
+
39
+
40
+ if __name__ == '__main__':
41
+ unittest.main()
@@ -1,2 +0,0 @@
1
-
2
- __version__ = '0.1.66'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes