nedo-vision-worker-core 0.3.0__tar.gz → 0.3.1__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.

Potentially problematic release.


This version of nedo-vision-worker-core might be problematic. Click here for more details.

Files changed (110) hide show
  1. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/PKG-INFO +1 -1
  2. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/__init__.py +1 -1
  3. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/cli.py +4 -56
  4. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/PipelineProcessor.py +248 -6
  5. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/VideoStream.py +62 -3
  6. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/PKG-INFO +1 -1
  7. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/MANIFEST.in +0 -0
  8. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/README.md +0 -0
  9. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/ai/FrameDrawer.py +0 -0
  10. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/ai/ImageDebugger.py +0 -0
  11. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/ai/VideoDebugger.py +0 -0
  12. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/ai/__init__.py +0 -0
  13. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/callbacks/DetectionCallbackManager.py +0 -0
  14. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/callbacks/DetectionCallbackTypes.py +0 -0
  15. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/callbacks/__init__.py +0 -0
  16. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/config/ConfigurationManager.py +0 -0
  17. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/config/__init__.py +0 -0
  18. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/core_service.py +0 -0
  19. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/database/DatabaseManager.py +0 -0
  20. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/database/__init__.py +0 -0
  21. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/BaseDetector.py +0 -0
  22. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/DetectionManager.py +0 -0
  23. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/RFDETRDetector.py +0 -0
  24. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/YOLODetector.py +0 -0
  25. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/__init__.py +0 -0
  26. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/detection_processing/DetectionProcessor.py +0 -0
  27. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/detection_processing/HumanDetectionProcessor.py +0 -0
  28. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/detection_processing/PPEDetectionProcessor.py +0 -0
  29. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/detection/detection_processing/__init__.py +0 -0
  30. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/doctor.py +0 -0
  31. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/blue/inner_corner.png +0 -0
  32. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/blue/inner_frame.png +0 -0
  33. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/blue/line.png +0 -0
  34. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/blue/top_left.png +0 -0
  35. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/blue/top_right.png +0 -0
  36. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/red/inner_corner.png +0 -0
  37. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/red/inner_frame.png +0 -0
  38. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/red/line.png +0 -0
  39. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/red/top_left.png +0 -0
  40. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/drawing_assets/red/top_right.png +0 -0
  41. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/boots-green.png +0 -0
  42. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/boots-red.png +0 -0
  43. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/gloves-green.png +0 -0
  44. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/gloves-red.png +0 -0
  45. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/goggles-green.png +0 -0
  46. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/goggles-red.png +0 -0
  47. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/helmet-green.png +0 -0
  48. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/helmet-red.png +0 -0
  49. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/mask-red.png +0 -0
  50. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/vest-green.png +0 -0
  51. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/icons/vest-red.png +0 -0
  52. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/__init__.py +0 -0
  53. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/ai_model.py +0 -0
  54. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/auth.py +0 -0
  55. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/config.py +0 -0
  56. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/dataset_source.py +0 -0
  57. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/logs.py +0 -0
  58. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/ppe_detection.py +0 -0
  59. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/ppe_detection_label.py +0 -0
  60. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/restricted_area_violation.py +0 -0
  61. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/user.py +0 -0
  62. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/worker_source.py +0 -0
  63. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/worker_source_pipeline.py +0 -0
  64. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/worker_source_pipeline_config.py +0 -0
  65. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/worker_source_pipeline_debug.py +0 -0
  66. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/models/worker_source_pipeline_detection.py +0 -0
  67. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/PipelineConfigManager.py +0 -0
  68. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/PipelineManager.py +0 -0
  69. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/PipelinePrepocessor.py +0 -0
  70. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/PipelineSyncThread.py +0 -0
  71. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/pipeline/__init__.py +0 -0
  72. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/preprocessing/ImageResizer.py +0 -0
  73. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/preprocessing/ImageRoi.py +0 -0
  74. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/preprocessing/Preprocessor.py +0 -0
  75. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/preprocessing/__init__.py +0 -0
  76. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/AIModelRepository.py +0 -0
  77. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/PPEDetectionRepository.py +0 -0
  78. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/RestrictedAreaRepository.py +0 -0
  79. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineDebugRepository.py +0 -0
  80. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineDetectionRepository.py +0 -0
  81. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/WorkerSourcePipelineRepository.py +0 -0
  82. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/WorkerSourceRepository.py +0 -0
  83. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/repositories/__init__.py +0 -0
  84. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/services/SharedVideoStreamServer.py +0 -0
  85. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/services/VideoSharingDaemon.py +0 -0
  86. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/services/VideoSharingDaemonManager.py +0 -0
  87. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/RTMPStreamer.py +0 -0
  88. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/SharedVideoDeviceManager.py +0 -0
  89. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/StreamSyncThread.py +0 -0
  90. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/VideoStreamManager.py +0 -0
  91. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/streams/__init__.py +0 -0
  92. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/tracker/SFSORT.py +0 -0
  93. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/tracker/TrackerManager.py +0 -0
  94. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/tracker/__init__.py +0 -0
  95. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/BoundingBoxMetrics.py +0 -0
  96. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/DrawingUtils.py +0 -0
  97. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/ModelReadinessChecker.py +0 -0
  98. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/PersonAttributeMatcher.py +0 -0
  99. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/PersonRestrictedAreaMatcher.py +0 -0
  100. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/TablePrinter.py +0 -0
  101. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core/util/__init__.py +0 -0
  102. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/SOURCES.txt +0 -0
  103. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/dependency_links.txt +0 -0
  104. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/entry_points.txt +0 -0
  105. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/requires.txt +0 -0
  106. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/nedo_vision_worker_core.egg-info/top_level.txt +0 -0
  107. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/pyproject.toml +0 -0
  108. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/requirements.txt +0 -0
  109. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/setup.cfg +0 -0
  110. {nedo_vision_worker_core-0.3.0 → nedo_vision_worker_core-0.3.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker-core
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Nedo Vision Worker Core Library for AI Vision Processing
5
5
  Author-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
6
6
  Maintainer-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
@@ -7,7 +7,7 @@ A library for running AI vision processing and detection in the Nedo Vision plat
7
7
  from .core_service import CoreService
8
8
  from .callbacks import DetectionType, CallbackTrigger, DetectionData, IntervalMetadata
9
9
 
10
- __version__ = "0.3.0"
10
+ __version__ = "0.3.1"
11
11
  __all__ = [
12
12
  "CoreService",
13
13
  "DetectionType",
@@ -91,61 +91,15 @@ Detection Callbacks:
91
91
 
92
92
  run_parser.add_argument(
93
93
  "--rtmp-server",
94
- default="rtmp://localhost:1935/live",
95
- help="RTMP server URL for video streaming (default: rtmp://localhost:1935/live)"
94
+ default="rtmp://live.vision.sindika.co.id:1935/live",
95
+ help="RTMP server URL for video streaming (default: rtmp://live.vision.sindika.co.id:1935/live)"
96
96
  )
97
97
 
98
98
  run_parser.add_argument(
99
- "--enable-video-sharing-daemon",
100
- action="store_true",
101
- default=True,
102
- help="Enable automatic video sharing daemon management (default: True)"
103
- )
104
-
105
- run_parser.add_argument(
106
- "--disable-video-sharing-daemon",
99
+ "--disable_video_sharing_daemon",
107
100
  action="store_true",
108
101
  default=False,
109
- help="Disable automatic video sharing daemon management"
110
- )
111
-
112
- # Add legacy arguments for backward compatibility (when no subcommand is used)
113
- parser.add_argument(
114
- "--drawing-assets",
115
- help="(Legacy) Path to drawing assets directory (optional, uses bundled assets by default)"
116
- )
117
-
118
- parser.add_argument(
119
- "--log-level",
120
- choices=["DEBUG", "INFO", "WARNING", "ERROR"],
121
- default="INFO",
122
- help="(Legacy) Logging level (default: INFO)"
123
- )
124
-
125
- parser.add_argument(
126
- "--storage-path",
127
- default="data",
128
- help="(Legacy) Storage path for databases and files (default: data)"
129
- )
130
-
131
- parser.add_argument(
132
- "--rtmp-server",
133
- default="rtmp://localhost:1935/live",
134
- help="(Legacy) RTMP server URL for video streaming (default: rtmp://localhost:1935/live)"
135
- )
136
-
137
- parser.add_argument(
138
- "--enable-video-sharing-daemon",
139
- action="store_true",
140
- default=True,
141
- help="(Legacy) Enable automatic video sharing daemon management (default: True)"
142
- )
143
-
144
- parser.add_argument(
145
- "--disable-video-sharing-daemon",
146
- action="store_true",
147
- default=False,
148
- help="(Legacy) Disable automatic video sharing daemon management"
102
+ help="Disable automatic video sharing daemon management (default: False)"
149
103
  )
150
104
 
151
105
  parser.add_argument(
@@ -161,11 +115,7 @@ Detection Callbacks:
161
115
  run_core_service(args)
162
116
  elif args.command == 'doctor':
163
117
  run_doctor()
164
- elif hasattr(args, 'drawing_assets') and args.drawing_assets is not None: # Legacy mode - if any arguments are provided without subcommand
165
- print("⚠️ Warning: Using legacy command format. Consider using 'nedo-core run --drawing-assets ...' instead.")
166
- run_core_service(args)
167
118
  else:
168
- # If no subcommand provided, show help
169
119
  parser.print_help()
170
120
  sys.exit(1)
171
121
 
@@ -197,8 +147,6 @@ def run_core_service(args):
197
147
  enable_daemon = True # Default
198
148
  if hasattr(args, 'disable_video_sharing_daemon') and args.disable_video_sharing_daemon:
199
149
  enable_daemon = False
200
- elif hasattr(args, 'enable_video_sharing_daemon'):
201
- enable_daemon = args.enable_video_sharing_daemon
202
150
 
203
151
  # Create and start the core service
204
152
  service = CoreService(
@@ -54,6 +54,17 @@ class PipelineProcessor:
54
54
  self.debug_flag = False
55
55
  self.debug_repo = WorkerSourcePipelineDebugRepository()
56
56
  self.detection_repo = WorkerSourcePipelineDetectionRepository()
57
+
58
+ # Frame recovery mechanism
59
+ self.consecutive_frame_failures = 0
60
+ self.max_consecutive_failures = 150 # 1.5 seconds at 0.01s intervals
61
+ self.last_successful_frame_time = time.time()
62
+ self.stream_recovery_timeout = 30.0 # 30 seconds timeout for stream recovery
63
+
64
+ # HEVC error tracking
65
+ self.hevc_error_count = 0
66
+ self.last_hevc_recovery = 0
67
+ self.hevc_recovery_cooldown = 30.0 # 30 seconds between HEVC recovery attempts
57
68
 
58
69
  def load_model(self, model):
59
70
  """
@@ -113,6 +124,10 @@ class PipelineProcessor:
113
124
  self.preprocessor.update(self.config_manager)
114
125
  self.detection_interval = self._get_detection_interval()
115
126
  self._update_detection_processor()
127
+
128
+ # Reset frame failure counters on config update
129
+ self.consecutive_frame_failures = 0
130
+ self.last_successful_frame_time = time.time()
116
131
 
117
132
  ai_model = self.detection_manager.model_metadata
118
133
 
@@ -142,6 +157,10 @@ class PipelineProcessor:
142
157
  logging.info(f"🎯 Running pipeline processing for pipeline {pipeline_id} | Source: {worker_source_id}")
143
158
 
144
159
  self._update_config()
160
+
161
+ # Reset failure counters at start
162
+ self.consecutive_frame_failures = 0
163
+ self.last_successful_frame_time = time.time()
145
164
 
146
165
  initial_frame = self._wait_for_frame(video_manager)
147
166
  if initial_frame is None:
@@ -163,14 +182,14 @@ class PipelineProcessor:
163
182
  frame = video_manager.get_frame(worker_source_id)
164
183
 
165
184
  if frame is None:
166
- logging.warning(f"⚠️ No frame available for {worker_source_id}. Retrying...")
167
- # Check if stream was removed
168
- if not video_manager.has_stream(worker_source_id):
169
- logging.info(f"🛑 Stream {worker_source_id} was removed, stopping pipeline")
185
+ if not self._handle_frame_failure(video_manager, worker_source_id):
170
186
  break
171
- time.sleep(0.01)
172
187
  continue
173
188
 
189
+ # Reset failure counters on successful frame
190
+ self.consecutive_frame_failures = 0
191
+ self.last_successful_frame_time = time.time()
192
+
174
193
  self.frame_counter += 1
175
194
 
176
195
  self.frame_drawer.draw_polygons(frame)
@@ -305,15 +324,200 @@ class PipelineProcessor:
305
324
 
306
325
  def _wait_for_frame(self, video_manager, max_retries=10, sleep_time=3):
307
326
  """Waits until a frame is available from the video source."""
327
+ logging.info(f"⏳ Waiting for initial frame from {self.worker_source_id}...")
328
+
308
329
  for retry_count in range(max_retries):
309
330
  frame = video_manager.get_frame(self.worker_source_id)
310
331
  if frame is not None:
332
+ logging.info(f"✅ Initial frame received from {self.worker_source_id}")
311
333
  return frame
334
+
335
+ # Check if stream exists
336
+ if not video_manager.has_stream(self.worker_source_id):
337
+ logging.error(f"❌ Stream {self.worker_source_id} not found in video manager")
338
+ return None
339
+
312
340
  logging.warning(f"⚠️ Waiting for video stream {self.worker_source_id} (Attempt {retry_count + 1}/{max_retries})...")
341
+
342
+ # Log stream diagnostics on later attempts
343
+ if retry_count >= 3:
344
+ self._log_stream_diagnostics(video_manager, self.worker_source_id)
345
+
313
346
  time.sleep(sleep_time)
314
347
 
348
+ logging.error(f"❌ Failed to get initial frame from {self.worker_source_id} after {max_retries} attempts")
315
349
  return None
316
350
 
351
+ def _handle_frame_failure(self, video_manager, worker_source_id):
352
+ """
353
+ Handle frame retrieval failures with progressive backoff and recovery attempts.
354
+ Returns False if pipeline should stop, True to continue.
355
+ """
356
+ self.consecutive_frame_failures += 1
357
+
358
+ # Check if stream was removed
359
+ if not video_manager.has_stream(worker_source_id):
360
+ logging.info(f"🛑 Stream {worker_source_id} was removed, stopping pipeline")
361
+ return False
362
+
363
+ # Check for stream recovery timeout
364
+ time_since_last_frame = time.time() - self.last_successful_frame_time
365
+ if time_since_last_frame > self.stream_recovery_timeout:
366
+ logging.error(f"❌ Stream {worker_source_id} recovery timeout ({self.stream_recovery_timeout}s). Stopping pipeline.")
367
+ return False
368
+
369
+ # Progressive logging and backoff
370
+ if self.consecutive_frame_failures <= 10:
371
+ # First 10 failures: minimal logging, fast retry
372
+ if self.consecutive_frame_failures % 5 == 1: # Log every 5th failure
373
+ logging.debug(f"⚠️ No frame available for {worker_source_id} (attempt {self.consecutive_frame_failures})")
374
+ time.sleep(0.01)
375
+ elif self.consecutive_frame_failures <= 50:
376
+ # 11-50 failures: moderate logging, slightly longer wait
377
+ if self.consecutive_frame_failures % 10 == 1: # Log every 10th failure
378
+ logging.warning(f"⚠️ No frame available for {worker_source_id} (attempt {self.consecutive_frame_failures}). Stream may be reconnecting...")
379
+ time.sleep(0.05)
380
+ elif self.consecutive_frame_failures <= self.max_consecutive_failures:
381
+ # 51-150 failures: more frequent logging, longer wait
382
+ if self.consecutive_frame_failures % 20 == 1: # Log every 20th failure
383
+ logging.warning(f"⚠️ Persistent frame issues for {worker_source_id} (attempt {self.consecutive_frame_failures}). Checking stream health...")
384
+ self._log_stream_diagnostics(video_manager, worker_source_id)
385
+
386
+ # Attempt HEVC recovery on severe persistent failures (every 60 failures to avoid too frequent reconnections)
387
+ if self.consecutive_frame_failures % 60 == 1:
388
+ # Check if we should attempt HEVC recovery based on error patterns and cooldown
389
+ if self._should_attempt_hevc_recovery(video_manager, worker_source_id):
390
+ logging.info(f"🔧 Attempting HEVC-specific recovery for persistent frame failures...")
391
+ recovery_success = self._handle_hevc_recovery(video_manager, worker_source_id)
392
+ if recovery_success:
393
+ logging.info(f"✅ HEVC recovery successful, continuing pipeline...")
394
+ return True # Continue processing after successful recovery
395
+
396
+ time.sleep(0.1)
397
+ else:
398
+ # Over max failures: critical logging and stop
399
+ logging.error(f"❌ Too many consecutive frame failures for {worker_source_id} ({self.consecutive_frame_failures}). Stopping pipeline.")
400
+ self._log_stream_diagnostics(video_manager, worker_source_id)
401
+ return False
402
+
403
+ return True
404
+
405
+ def _log_stream_diagnostics(self, video_manager, worker_source_id):
406
+ """Log diagnostic information about the stream state."""
407
+ try:
408
+ stream_url = video_manager.get_stream_url(worker_source_id)
409
+ is_file = video_manager.is_video_file(worker_source_id)
410
+
411
+ # Get stream object for more detailed diagnostics
412
+ if hasattr(video_manager, 'streams') and worker_source_id in video_manager.streams:
413
+ stream = video_manager.streams[worker_source_id]
414
+ state = stream.get_state() if hasattr(stream, 'get_state') else "unknown"
415
+ is_connected = stream.is_connected() if hasattr(stream, 'is_connected') else "unknown"
416
+
417
+ logging.info(f"📊 Stream diagnostics for {worker_source_id}:")
418
+ logging.info(f" URL: {stream_url}")
419
+ logging.info(f" Type: {'Video file' if is_file else 'Live stream'}")
420
+ logging.info(f" State: {state}")
421
+ logging.info(f" Connected: {is_connected}")
422
+ logging.info(f" Time since last frame: {time.time() - self.last_successful_frame_time:.1f}s")
423
+
424
+ # Check for HEVC/codec specific issues
425
+ if hasattr(stream, 'get_codec_info'):
426
+ codec_info = stream.get_codec_info()
427
+ if codec_info:
428
+ logging.info(f" Codec: {codec_info}")
429
+ if 'hevc' in str(codec_info).lower() or 'h265' in str(codec_info).lower():
430
+ logging.warning(f" ⚠️ HEVC stream detected - may experience QP delta or POC reference errors")
431
+
432
+ # Log recent error patterns if available
433
+ if hasattr(stream, 'get_recent_errors'):
434
+ recent_errors = stream.get_recent_errors()
435
+ if recent_errors:
436
+ hevc_errors = [err for err in recent_errors if 'cu_qp_delta' in str(err.get('error', '')) or 'Could not find ref with POC' in str(err.get('error', ''))]
437
+ if hevc_errors:
438
+ logging.warning(f" 🔥 Recent HEVC errors detected: {len(hevc_errors)} codec-related errors")
439
+ self.hevc_error_count += len(hevc_errors)
440
+
441
+ # Log sample of recent HEVC errors for debugging
442
+ for i, err in enumerate(hevc_errors[-3:]): # Show last 3 errors
443
+ logging.warning(f" 🔥 HEVC Error {i+1}: {err.get('error', '')[:100]}...")
444
+ else:
445
+ logging.info(f"📊 Stream {worker_source_id} not found in regular streams, checking direct device streams...")
446
+
447
+ except Exception as e:
448
+ logging.error(f"Error getting stream diagnostics: {e}")
449
+
450
+ def _should_attempt_hevc_recovery(self, video_manager, worker_source_id) -> bool:
451
+ """
452
+ Determine if HEVC recovery should be attempted based on error patterns and cooldown.
453
+ """
454
+ current_time = time.time()
455
+
456
+ # Check cooldown period
457
+ if current_time - self.last_hevc_recovery < self.hevc_recovery_cooldown:
458
+ logging.debug(f"HEVC recovery on cooldown ({current_time - self.last_hevc_recovery:.1f}s elapsed)")
459
+ return False
460
+
461
+ # Check if stream has HEVC-related errors
462
+ if hasattr(video_manager, 'streams') and worker_source_id in video_manager.streams:
463
+ stream = video_manager.streams[worker_source_id]
464
+ if hasattr(stream, 'get_recent_errors'):
465
+ recent_errors = stream.get_recent_errors(max_age_seconds=60) # Last minute
466
+ hevc_errors = [err for err in recent_errors if
467
+ 'cu_qp_delta' in str(err.get('error', '')) or
468
+ 'Could not find ref with POC' in str(err.get('error', ''))]
469
+
470
+ if len(hevc_errors) >= 3: # Threshold for HEVC errors
471
+ logging.info(f"HEVC recovery warranted: {len(hevc_errors)} HEVC errors in last minute")
472
+ return True
473
+
474
+ # Check if we have accumulated enough general HEVC errors
475
+ if self.hevc_error_count >= 5:
476
+ logging.info(f"HEVC recovery warranted: {self.hevc_error_count} total HEVC errors detected")
477
+ return True
478
+
479
+ return False
480
+
481
+ def _handle_hevc_recovery(self, video_manager, worker_source_id):
482
+ """
483
+ Handle HEVC-specific recovery strategies for codec errors.
484
+ This method attempts to recover from common HEVC issues like QP delta and POC reference errors.
485
+ """
486
+ try:
487
+ self.last_hevc_recovery = time.time() # Update recovery timestamp
488
+ logging.info(f"🔧 Attempting HEVC stream recovery for {worker_source_id}")
489
+
490
+ # Get the stream URL for recreation
491
+ stream_url = video_manager.get_stream_url(worker_source_id)
492
+ if not stream_url:
493
+ logging.error(f" Cannot get stream URL for {worker_source_id}")
494
+ return False
495
+
496
+ # Strategy 1: Remove and re-add the stream to reset decoder state
497
+ logging.info(f" Recreating stream {worker_source_id} to reset decoder state...")
498
+ video_manager.remove_stream(worker_source_id)
499
+ time.sleep(1.0) # Give time for cleanup
500
+
501
+ # Re-add the stream
502
+ video_manager.add_stream(worker_source_id, stream_url)
503
+ time.sleep(2.0) # Give time for stream to initialize
504
+
505
+ # Strategy 2: Check if stream was successfully recreated
506
+ if not video_manager.has_stream(worker_source_id):
507
+ logging.error(f" Failed to recreate stream {worker_source_id}")
508
+ return False
509
+
510
+ # Strategy 3: Reset failure counters and error counts after recovery attempt
511
+ self.reset_frame_failure_counters()
512
+ self.hevc_error_count = 0 # Reset HEVC error counter
513
+
514
+ logging.info(f"✅ HEVC recovery attempt completed for {worker_source_id}")
515
+ return True
516
+
517
+ except Exception as e:
518
+ logging.error(f"❌ HEVC recovery failed for {worker_source_id}: {e}")
519
+ return False
520
+
317
521
  def stop(self):
318
522
  """Stops the Pipeline processor and cleans up resources."""
319
523
  if not self.running: # Prevent multiple stops
@@ -378,4 +582,42 @@ class PipelineProcessor:
378
582
 
379
583
  def enable_debug(self):
380
584
  """Enable debug mode for this pipeline."""
381
- self.debug_flag = True
585
+ self.debug_flag = True
586
+ # Reset failure counters when debug is enabled as it may help with recovery
587
+ self.consecutive_frame_failures = 0
588
+ self.last_successful_frame_time = time.time()
589
+
590
+ def reset_frame_failure_counters(self):
591
+ """Reset frame failure counters. Can be called externally to help with recovery."""
592
+ logging.info(f"🔄 Resetting frame failure counters for pipeline {self.pipeline_id}")
593
+ self.consecutive_frame_failures = 0
594
+ self.last_successful_frame_time = time.time()
595
+ self.hevc_error_count = 0 # Also reset HEVC error count
596
+
597
+ def get_hevc_diagnostics(self, video_manager) -> dict:
598
+ """Get HEVC-specific diagnostics for the pipeline."""
599
+ diagnostics = {
600
+ 'hevc_error_count': self.hevc_error_count,
601
+ 'last_hevc_recovery': self.last_hevc_recovery,
602
+ 'time_since_last_recovery': time.time() - self.last_hevc_recovery,
603
+ 'recovery_cooldown_remaining': max(0, self.hevc_recovery_cooldown - (time.time() - self.last_hevc_recovery)),
604
+ 'consecutive_failures': self.consecutive_frame_failures,
605
+ 'time_since_last_frame': time.time() - self.last_successful_frame_time,
606
+ }
607
+
608
+ # Add stream-specific HEVC information
609
+ if hasattr(video_manager, 'streams') and self.worker_source_id in video_manager.streams:
610
+ stream = video_manager.streams[self.worker_source_id]
611
+
612
+ if hasattr(stream, 'get_codec_info'):
613
+ diagnostics['codec'] = stream.get_codec_info()
614
+
615
+ if hasattr(stream, 'get_recent_errors'):
616
+ recent_errors = stream.get_recent_errors(max_age_seconds=300) # Last 5 minutes
617
+ hevc_errors = [err for err in recent_errors if
618
+ 'cu_qp_delta' in str(err.get('error', '')) or
619
+ 'Could not find ref with POC' in str(err.get('error', ''))]
620
+ diagnostics['recent_hevc_errors'] = len(hevc_errors)
621
+ diagnostics['total_recent_errors'] = len(recent_errors)
622
+
623
+ return diagnostics
@@ -50,6 +50,11 @@ class VideoStream(threading.Thread):
50
50
 
51
51
  self.is_file = self._is_file_source()
52
52
 
53
+ # Error tracking for diagnostics
54
+ self._recent_errors = []
55
+ self._max_error_history = 50
56
+ self._codec_info = None
57
+
53
58
  def _is_file_source(self) -> bool:
54
59
  """Check if source is a file path."""
55
60
  if isinstance(self.source, int):
@@ -90,6 +95,32 @@ class VideoStream(threading.Thread):
90
95
  """Configure capture properties and determine FPS."""
91
96
  detected_fps = self.capture.get(cv2.CAP_PROP_FPS)
92
97
 
98
+ # Configure error-resilient settings for video streams
99
+ if not self.is_file:
100
+ try:
101
+ # Set buffer size to reduce latency and handle corrupted frames better
102
+ self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
103
+
104
+ # Set codec-specific properties for better error handling
105
+ # These help with HEVC streams that have QP delta and POC reference errors
106
+ if hasattr(cv2, 'CAP_PROP_FOURCC'):
107
+ # Try to get current codec
108
+ fourcc = self.capture.get(cv2.CAP_PROP_FOURCC)
109
+ codec_str = "".join([chr((int(fourcc) >> 8 * i) & 0xFF) for i in range(4)])
110
+
111
+ # Log codec information for debugging
112
+ logging.info(f"Stream codec detected: {codec_str} (fourcc: {int(fourcc)})")
113
+ self._codec_info = codec_str
114
+
115
+ # For HEVC streams, we can't change the codec but we can optimize buffering
116
+ if 'hevc' in codec_str.lower() or 'h265' in codec_str.lower():
117
+ logging.info("HEVC stream detected - applying error-resilient settings")
118
+ # Reduce buffer to minimize impact of corrupted frames
119
+ self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 1)
120
+
121
+ except Exception as e:
122
+ logging.warning(f"Could not configure capture properties: {e}")
123
+
93
124
  if self.target_fps:
94
125
  self.fps = self.target_fps
95
126
  if hasattr(cv2, 'CAP_PROP_FPS'):
@@ -205,13 +236,17 @@ class VideoStream(threading.Thread):
205
236
  break
206
237
 
207
238
  except cv2.error as e:
208
- logging.error(f"OpenCV error: {e}")
239
+ error_msg = f"OpenCV error: {e}"
240
+ logging.error(error_msg)
241
+ self._add_error_to_history(error_msg)
209
242
  self._cleanup_capture()
210
243
  if not self._sleep_interruptible(1.0):
211
244
  break
212
245
 
213
246
  except Exception as e:
214
- logging.error(f"Unexpected error: {e}", exc_info=True)
247
+ error_msg = f"Unexpected error: {e}"
248
+ logging.error(error_msg, exc_info=True)
249
+ self._add_error_to_history(error_msg)
215
250
  if not self._sleep_interruptible(self.reconnect_interval):
216
251
  break
217
252
 
@@ -283,4 +318,28 @@ class VideoStream(threading.Thread):
283
318
 
284
319
  def __exit__(self, exc_type, exc_val, exc_tb):
285
320
  """Context manager exit."""
286
- self.stop()
321
+ self.stop()
322
+
323
+ def _add_error_to_history(self, error_msg: str):
324
+ """Add error to recent error history for diagnostics."""
325
+ with self._lock:
326
+ self._recent_errors.append({
327
+ 'timestamp': time.time(),
328
+ 'error': error_msg
329
+ })
330
+ # Keep only recent errors
331
+ if len(self._recent_errors) > self._max_error_history:
332
+ self._recent_errors = self._recent_errors[-self._max_error_history:]
333
+
334
+ def get_recent_errors(self, max_age_seconds: float = 300) -> list:
335
+ """Get recent errors within the specified time window."""
336
+ current_time = time.time()
337
+ with self._lock:
338
+ return [
339
+ err for err in self._recent_errors
340
+ if current_time - err['timestamp'] <= max_age_seconds
341
+ ]
342
+
343
+ def get_codec_info(self) -> Optional[str]:
344
+ """Get codec information for the stream."""
345
+ return self._codec_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker-core
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Nedo Vision Worker Core Library for AI Vision Processing
5
5
  Author-email: Willy Achmat Fauzi <willy.achmat@gmail.com>
6
6
  Maintainer-email: Willy Achmat Fauzi <willy.achmat@gmail.com>