nedo-vision-worker 1.2.7__py3-none-any.whl → 1.2.9__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.
@@ -6,5 +6,5 @@ A library for running worker agents in the Nedo Vision platform.
6
6
 
7
7
  from .worker_service import WorkerService
8
8
 
9
- __version__ = "1.2.7"
9
+ __version__ = "1.2.9"
10
10
  __all__ = ["WorkerService"]
@@ -76,3 +76,63 @@ class AppInitializer:
76
76
  logging.error(f"Grpc Error: {ge}")
77
77
  except Exception as e:
78
78
  logging.error(f"Unexpected error during initialization: {e}")
79
+
80
+ @staticmethod
81
+ def update_connection_info(server_host: str, server_port: int, token: str):
82
+ """
83
+ Fetch and update connection information (RabbitMQ credentials) from the server.
84
+ This should be called on startup to ensure credentials are up-to-date.
85
+
86
+ Args:
87
+ server_host: The server hostname or IP address
88
+ server_port: The gRPC server port
89
+ token: Authentication token for the worker
90
+
91
+ Returns:
92
+ bool: True if update was successful, False otherwise
93
+ """
94
+ try:
95
+ # Validate server host
96
+ AppInitializer.validate_server_host(server_host)
97
+
98
+ # Get connection info using the ConnectionInfoClient
99
+ connection_client = ConnectionInfoClient(server_host, server_port, token)
100
+ connection_result = connection_client.get_connection_info()
101
+
102
+ if not connection_result["success"]:
103
+ logging.error(f"Failed to fetch connection info: {connection_result['message']}")
104
+ return False
105
+
106
+ # Check if any RabbitMQ credentials have changed
107
+ current_config = ConfigurationManager.get_all_configs()
108
+ config_updated = False
109
+
110
+ rabbitmq_fields = {
111
+ 'rabbitmq_host': connection_result['rabbitmq_host'],
112
+ 'rabbitmq_port': str(connection_result['rabbitmq_port']),
113
+ 'rabbitmq_username': connection_result['rabbitmq_username'],
114
+ 'rabbitmq_password': connection_result['rabbitmq_password']
115
+ }
116
+
117
+ for field, new_value in rabbitmq_fields.items():
118
+ if current_config.get(field) != new_value:
119
+ ConfigurationManager.set_config(field, new_value)
120
+ config_updated = True
121
+ logging.info(f"✅ [APP] Updated {field}")
122
+
123
+ if config_updated:
124
+ logging.info("✅ [APP] RabbitMQ connection info updated successfully")
125
+ else:
126
+ logging.info("✅ [APP] RabbitMQ connection info is up-to-date")
127
+
128
+ return True
129
+
130
+ except ValueError as ve:
131
+ logging.error(f"Validation error: {ve}")
132
+ return False
133
+ except grpc.RpcError as ge:
134
+ logging.error(f"gRPC Error: {ge}")
135
+ return False
136
+ except Exception as e:
137
+ logging.error(f"Unexpected error updating connection info: {e}")
138
+ return False
@@ -77,6 +77,10 @@ class PPEDetectionRepository:
77
77
  Args:
78
78
  detection_data (list): List of dictionaries containing the detection data.
79
79
  """
80
+ if not detection_data:
81
+ logging.info("No detection data provided for deletion.")
82
+ return
83
+
80
84
  try:
81
85
  # Extract person_id from detection data to delete the corresponding records
82
86
  person_ids_to_delete = [data['person_id'] for data in detection_data]
@@ -97,10 +101,25 @@ class PPEDetectionRepository:
97
101
  if not os.path.isabs(image_path):
98
102
  image_path = str(get_storage_path("files") / Path(image_path).relative_to("data/files"))
99
103
  if os.path.exists(image_path):
100
- os.remove(image_path)
101
- logging.info(f"Deleted image file: {image_path}")
104
+ try:
105
+ os.remove(image_path)
106
+ logging.info(f"Deleted image file: {image_path}")
107
+ except OSError as e:
108
+ logging.warning(f"Failed to delete image file {image_path}: {e}")
102
109
  else:
103
110
  logging.warning(f"Image file not found for detection {detection.id}: {image_path}")
111
+
112
+ # Delete the image tile file if it exists
113
+ image_tile_path = detection.image_tile_path
114
+ if image_tile_path:
115
+ if not os.path.isabs(image_tile_path):
116
+ image_tile_path = str(get_storage_path("files") / Path(image_tile_path).relative_to("data/files"))
117
+ if os.path.exists(image_tile_path):
118
+ try:
119
+ os.remove(image_tile_path)
120
+ logging.info(f"Deleted image tile file: {image_tile_path}")
121
+ except OSError as e:
122
+ logging.warning(f"Failed to delete image tile file {image_tile_path}: {e}")
104
123
 
105
124
  # Delete the detection record
106
125
  self.session.delete(detection)
@@ -61,6 +61,10 @@ class RestrictedAreaRepository:
61
61
  Args:
62
62
  violation_data (list): List of dictionaries containing the violation data.
63
63
  """
64
+ if not violation_data:
65
+ logging.info("No violation data provided for deletion.")
66
+ return
67
+
64
68
  try:
65
69
  person_ids_to_delete = [data['person_id'] for data in violation_data]
66
70
 
@@ -75,11 +79,26 @@ class RestrictedAreaRepository:
75
79
  if not os.path.isabs(image_path):
76
80
  image_path = str(get_storage_path("restricted_violations") / Path(image_path).relative_to("data/restricted_violations"))
77
81
  if os.path.exists(image_path):
78
- os.remove(image_path)
79
- logging.info(f"Deleted image file: {image_path}")
82
+ try:
83
+ os.remove(image_path)
84
+ logging.info(f"Deleted image file: {image_path}")
85
+ except OSError as e:
86
+ logging.warning(f"Failed to delete image file {image_path}: {e}")
80
87
  else:
81
88
  logging.warning(f"Image file not found for violation {violation.id}: {image_path}")
82
89
 
90
+ # Delete the image tile file if it exists
91
+ image_tile_path = violation.image_tile_path
92
+ if image_tile_path:
93
+ if not os.path.isabs(image_tile_path):
94
+ image_tile_path = str(get_storage_path("restricted_violations") / Path(image_tile_path).relative_to("data/restricted_violations"))
95
+ if os.path.exists(image_tile_path):
96
+ try:
97
+ os.remove(image_tile_path)
98
+ logging.info(f"Deleted image tile file: {image_tile_path}")
99
+ except OSError as e:
100
+ logging.warning(f"Failed to delete image tile file {image_tile_path}: {e}")
101
+
83
102
  self.session.delete(violation)
84
103
 
85
104
  self.session.commit()
@@ -8,6 +8,7 @@ import cv2
8
8
  import os
9
9
  from .VideoSharingDaemon import VideoSharingClient
10
10
  from ..database.DatabaseManager import get_storage_path
11
+ from ..util.EncoderSelector import EncoderSelector
11
12
 
12
13
 
13
14
  class DirectDeviceToRTMPStreamer:
@@ -68,6 +69,10 @@ class DirectDeviceToRTMPStreamer:
68
69
 
69
70
  def _start_ffmpeg_stream(self):
70
71
  """Starts an FFmpeg process to stream frames to the RTMP server silently."""
72
+ # Get optimal encoder for hardware
73
+ encoder_args, encoder_name = EncoderSelector.get_encoder_args()
74
+ logging.info(f"🎬 [APP] Using encoder: {encoder_name}")
75
+
71
76
  ffmpeg_command = [
72
77
  "ffmpeg",
73
78
  "-y",
@@ -79,16 +84,17 @@ class DirectDeviceToRTMPStreamer:
79
84
  "-video_size", f"{self.width}x{self.height}",
80
85
  "-framerate", str(self.fps),
81
86
  "-i", "-",
82
- "-c:v", "libx264",
83
- "-preset", "ultrafast",
84
- "-tune", "zerolatency",
87
+
88
+ # Video encoding with optimal encoder
89
+ *encoder_args,
85
90
  "-b:v", self.bitrate,
86
- # Disable Audio (Avoid unnecessary encoding overhead)
87
- "-an",
88
91
  "-maxrate", "2500k",
89
92
  "-bufsize", "5000k",
93
+
94
+ # Disable Audio
95
+ "-an",
96
+
90
97
  "-f", "flv",
91
- # Remove duration limit - let application control duration
92
98
  self.rtmp_url,
93
99
  ]
94
100
 
@@ -1,6 +1,7 @@
1
1
  import subprocess
2
2
  import logging
3
3
  import os
4
+ from ..util.EncoderSelector import EncoderSelector
4
5
 
5
6
  class FileToRTMPStreamer:
6
7
  def __init__(self, video_path, rtmp_url, stream_key, fps=30, resolution="1280x720", loop=False):
@@ -31,6 +32,10 @@ class FileToRTMPStreamer:
31
32
 
32
33
  logging.info(f"📼 [APP] Starting file stream: {self.video_path} → {self.rtmp_url}")
33
34
 
35
+ # Get optimal encoder for hardware
36
+ encoder_args, encoder_name = EncoderSelector.get_encoder_args()
37
+ logging.info(f"🎬 [APP] Using encoder: {encoder_name}")
38
+
34
39
  # FFmpeg command
35
40
  ffmpeg_command = [
36
41
  "ffmpeg",
@@ -38,9 +43,8 @@ class FileToRTMPStreamer:
38
43
  "-stream_loop", "-1" if self.loop else "0", # Loop if needed
39
44
  "-i", self.video_path,
40
45
 
41
- "-c:v", "libx264",
42
- "-preset", "ultrafast",
43
- "-tune", "zerolatency",
46
+ # Video encoding with optimal encoder
47
+ *encoder_args,
44
48
  "-r", str(self.fps),
45
49
  "-b:v", "1500k",
46
50
  "-maxrate", "2000k",
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from .GrpcClientBase import GrpcClientBase
3
4
  from ..protos.PPEDetectionService_pb2_grpc import PPEDetectionGRPCServiceStub
4
5
  from ..protos.PPEDetectionService_pb2 import UpsertPPEDetectionBatchRequest, UpsertPPEDetectionRequest, PPEDetectionLabelRequest
@@ -56,11 +57,23 @@ class PPEDetectionClient(GrpcClientBase):
56
57
  return {"success": False, "message": "gRPC connection is not established."}
57
58
 
58
59
  try:
59
- # Prepare the list of UpsertPPEDetectionRequest messages
60
60
  ppe_detection_requests = []
61
+ valid_records = []
62
+ invalid_records = []
63
+
61
64
  for data in detection_data:
62
- image_binary = self.read_image_as_binary(data['image'])
63
- image_tile_binary = self.read_image_as_binary(data['image_tile'])
65
+ if not os.path.exists(data['image']) or not os.path.exists(data['image_tile']):
66
+ logger.warning(f"⚠️ Missing image files for person_id {data.get('person_id')}")
67
+ invalid_records.append(data)
68
+ continue
69
+
70
+ try:
71
+ image_binary = self.read_image_as_binary(data['image'])
72
+ image_tile_binary = self.read_image_as_binary(data['image_tile'])
73
+ except Exception as e:
74
+ logger.error(f"❌ Error reading images for person_id {data.get('person_id')}: {e}")
75
+ invalid_records.append(data)
76
+ continue
64
77
 
65
78
  ppe_detection_labels = [
66
79
  PPEDetectionLabelRequest(
@@ -73,11 +86,11 @@ class PPEDetectionClient(GrpcClientBase):
73
86
  )
74
87
  for label in data['ppe_detection_labels']
75
88
  ]
76
- source = data['worker_source_id']
89
+
77
90
  request = UpsertPPEDetectionRequest(
78
91
  person_id=data['person_id'],
79
92
  worker_id=worker_id,
80
- worker_source_id=source,
93
+ worker_source_id=data['worker_source_id'],
81
94
  image=image_binary,
82
95
  image_tile=image_tile_binary,
83
96
  worker_timestamp=data['worker_timestamp'],
@@ -85,18 +98,24 @@ class PPEDetectionClient(GrpcClientBase):
85
98
  token=token
86
99
  )
87
100
  ppe_detection_requests.append(request)
101
+ valid_records.append(data)
102
+
103
+ if invalid_records:
104
+ logger.info(f"🧹 Deleting {len(invalid_records)} invalid PPE detection records")
105
+ self.repository.delete_records_from_db(invalid_records)
106
+
107
+ if not ppe_detection_requests:
108
+ return {"success": True, "message": "No valid detections to send"}
88
109
 
89
- # Create the UpsertPPEDetectionBatchRequest
90
110
  batch_request = UpsertPPEDetectionBatchRequest(
91
111
  ppe_detection_requests=ppe_detection_requests,
92
112
  token=token
93
113
  )
94
114
 
95
- # Call the UpsertBatch RPC
96
115
  response = self.handle_rpc(self.stub.UpsertBatch, batch_request)
97
116
 
98
117
  if response and response.success:
99
- self.repository.delete_records_from_db(detection_data)
118
+ self.repository.delete_records_from_db(valid_records)
100
119
  return {"success": True, "message": response.message}
101
120
 
102
121
  return {"success": False, "message": response.message if response else "Unknown error"}
@@ -3,6 +3,7 @@ import logging
3
3
  import time
4
4
  import os
5
5
  from urllib.parse import urlparse
6
+ from ..util.EncoderSelector import EncoderSelector
6
7
 
7
8
  class RTSPtoRTMPStreamer:
8
9
  def __init__(self, rtsp_url, rtmp_url, stream_key, fps=30, resolution="1280x720", duration=120):
@@ -38,6 +39,10 @@ class RTSPtoRTMPStreamer:
38
39
 
39
40
  logging.info(f"📡 [APP] Starting RTSP to RTMP stream: {self.rtsp_url} → {self.rtmp_url} for {self.duration} seconds")
40
41
 
42
+ # Get optimal encoder for hardware
43
+ encoder_args, encoder_name = EncoderSelector.get_encoder_args()
44
+ logging.info(f"🎬 [APP] Using encoder: {encoder_name}")
45
+
41
46
  # FFmpeg command
42
47
  ffmpeg_command = [
43
48
  "ffmpeg",
@@ -47,17 +52,14 @@ class RTSPtoRTMPStreamer:
47
52
  "-strict", "experimental",
48
53
  "-i", self.rtsp_url,
49
54
 
50
- # Video Encoding (Fastest possible)
51
- "-c:v", "libx264",
52
- "-preset", "ultrafast", # 🚀 Reduce CPU usage
53
- "-tune", "zerolatency", # 🚀 Optimize for real-time streaming
54
- "-x264-params", "keyint=40:min-keyint=40", # 🚀 Keyframe optimization
55
- "-r", "25", # ⏳ Limit FPS to 20 (prevents excessive encoding load)
56
- "-b:v", "1500k", # ✅ Lower bitrate to improve performance
55
+ # Video encoding with optimal encoder
56
+ *encoder_args,
57
+ "-r", "25", # Limit FPS to 25
58
+ "-b:v", "1500k", # Bitrate
57
59
  "-maxrate", "2000k", # ✅ Set max bitrate
58
60
  "-bufsize", "4000k", # ✅ Reduce buffer latency
59
61
  "-g", "25", # ✅ Reduce GOP size for faster keyframes
60
- "-vf", "scale='min(1024,iw)':-2", # ✅ Resize width to max 800px
62
+ "-vf", "scale='min(1024,iw)':-2", # ✅ Resize width to max 1024px
61
63
 
62
64
  # ❌ Disable Audio (Avoid unnecessary encoding overhead)
63
65
  "-an",
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ import os
2
3
  from ..repositories.RestrictedAreaRepository import RestrictedAreaRepository
3
4
  from .GrpcClientBase import GrpcClientBase
4
5
  from ..protos.HumanDetectionService_pb2_grpc import HumanDetectionGRPCServiceStub
@@ -60,10 +61,22 @@ class RestrictedAreaClient(GrpcClientBase):
60
61
 
61
62
  try:
62
63
  human_detection_requests = []
64
+ valid_records = []
65
+ invalid_records = []
63
66
 
64
67
  for data in violation_data:
65
- image_binary = self.read_image_as_binary(data['image'])
66
- image_tile_binary = self.read_image_as_binary(data['image_tile'])
68
+ if not os.path.exists(data['image']) or not os.path.exists(data['image_tile']):
69
+ logger.warning(f"⚠️ Missing image files for person_id {data.get('person_id')}")
70
+ invalid_records.append(data)
71
+ continue
72
+
73
+ try:
74
+ image_binary = self.read_image_as_binary(data['image'])
75
+ image_tile_binary = self.read_image_as_binary(data['image_tile'])
76
+ except Exception as e:
77
+ logger.error(f"❌ Error reading images for person_id {data.get('person_id')}: {e}")
78
+ invalid_records.append(data)
79
+ continue
67
80
 
68
81
  request = UpsertHumanDetectionRequest(
69
82
  person_id=data['person_id'],
@@ -81,6 +94,14 @@ class RestrictedAreaClient(GrpcClientBase):
81
94
  )
82
95
 
83
96
  human_detection_requests.append(request)
97
+ valid_records.append(data)
98
+
99
+ if invalid_records:
100
+ logger.info(f"🧹 Deleting {len(invalid_records)} invalid violation records")
101
+ self.repository.delete_records_from_db(invalid_records)
102
+
103
+ if not human_detection_requests:
104
+ return {"success": True, "message": "No valid violations to send"}
84
105
 
85
106
  batch_request = UpsertHumanDetectionBatchRequest(
86
107
  human_detection_requests=human_detection_requests,
@@ -90,7 +111,7 @@ class RestrictedAreaClient(GrpcClientBase):
90
111
  response = self.handle_rpc(self.stub.UpsertBatch, batch_request)
91
112
 
92
113
  if response and response.success:
93
- self.repository.delete_records_from_db(violation_data)
114
+ self.repository.delete_records_from_db(valid_records)
94
115
  return {"success": True, "message": response.message}
95
116
 
96
117
  return {"success": False, "message": response.message if response else "Unknown error"}
@@ -377,7 +377,15 @@ class WorkerSourcePipelineClient(GrpcClientBase):
377
377
  debug_entries = self.debug_repo.get_debug_entries_with_data()
378
378
 
379
379
  for debug_entry in debug_entries:
380
- image_binary = self.read_image_as_binary(debug_entry.image_path)
380
+ try:
381
+ image_binary = self.read_image_as_binary(debug_entry.image_path)
382
+ except FileNotFoundError:
383
+ logging.warning(f"Image file not found: {debug_entry.image_path}, deleting entry {debug_entry.id}")
384
+ self.debug_repo.delete_entry_by_id(debug_entry.id)
385
+ continue
386
+ except Exception as e:
387
+ logging.error(f"Error reading image {debug_entry.image_path}: {e}")
388
+ continue
381
389
 
382
390
  request = SendPipelineDebugRequest(
383
391
  worker_source_pipeline_id=debug_entry.worker_source_pipeline_id,
@@ -391,12 +399,13 @@ class WorkerSourcePipelineClient(GrpcClientBase):
391
399
  if response and response.success:
392
400
  self.debug_repo.delete_entry_by_id(debug_entry.id)
393
401
  else:
394
- return {"success": False, "message": response.message if response else "Unknown error"}
402
+ logging.warning(f"Failed to sync debug entry {debug_entry.id}: {response.message if response else 'Unknown error'}")
395
403
 
396
404
  return {"success": True, "message": "Successfully synced debug entries"}
397
405
 
398
406
  except Exception as e:
399
- logging.error(f"Error syncing pipeline debug: {e}")
407
+ logging.error(f"Error syncing pipeline debug: {e}", exc_info=True)
408
+ return {"success": False, "message": f"Exception: {str(e)}"}
400
409
 
401
410
  def sync_pipeline_detection(self, token: str):
402
411
  if not self.stub:
@@ -406,7 +415,15 @@ class WorkerSourcePipelineClient(GrpcClientBase):
406
415
  entries = self.detection_repo.get_entries()
407
416
 
408
417
  for entry in entries:
409
- image_binary = self.read_image_as_binary(entry.image_path)
418
+ try:
419
+ image_binary = self.read_image_as_binary(entry.image_path)
420
+ except FileNotFoundError:
421
+ logging.warning(f"Image file not found: {entry.image_path}, deleting entry {entry.id}")
422
+ self.detection_repo.delete_entry_by_id(entry.id)
423
+ continue
424
+ except Exception as e:
425
+ logging.error(f"Error reading image {entry.image_path}: {e}")
426
+ continue
410
427
 
411
428
  request = SendPipelineDetectionDataRequest(
412
429
  worker_source_pipeline_id=entry.worker_source_pipeline_id,
@@ -420,9 +437,10 @@ class WorkerSourcePipelineClient(GrpcClientBase):
420
437
  if response and response.success:
421
438
  self.detection_repo.delete_entry_by_id(entry.id)
422
439
  else:
423
- return {"success": False, "message": response.message if response else "Unknown error"}
440
+ logging.warning(f"Failed to sync detection entry {entry.id}: {response.message if response else 'Unknown error'}")
424
441
 
425
- return {"success": True, "message": "Successfully synced debug entries"}
442
+ return {"success": True, "message": "Successfully synced detection entries"}
426
443
 
427
444
  except Exception as e:
428
- logging.error(f"Error syncing pipeline debug: {e}")
445
+ logging.error(f"Error syncing pipeline detection: {e}", exc_info=True)
446
+ return {"success": False, "message": f"Exception: {str(e)}"}
@@ -0,0 +1,109 @@
1
+ """
2
+ Utility for selecting the best available video encoder (GPU or CPU fallback).
3
+ """
4
+ import os
5
+ import sys
6
+ import logging
7
+ from typing import List, Tuple
8
+ from .PlatformDetector import PlatformDetector
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ class EncoderSelector:
14
+ """Selects optimal video encoder based on available hardware."""
15
+
16
+ _platform = PlatformDetector()
17
+
18
+ @classmethod
19
+ def get_encoder_args(cls, force_cpu: bool = False) -> Tuple[List[str], str]:
20
+ """
21
+ Get FFmpeg encoder arguments.
22
+
23
+ Args:
24
+ force_cpu: Force CPU encoding even if GPU is available
25
+
26
+ Returns:
27
+ Tuple of (encoder_args_list, encoder_name)
28
+ """
29
+ if force_cpu:
30
+ return cls._get_cpu_encoder()
31
+
32
+ # Check environment variable override
33
+ force_encoder = os.environ.get("RTMP_ENCODER", "").lower()
34
+
35
+ if force_encoder == "cpu" or force_encoder == "libx264":
36
+ return cls._get_cpu_encoder()
37
+ elif force_encoder == "nvenc":
38
+ return cls._get_nvenc_encoder()
39
+
40
+ # Jetson platform
41
+ if cls._platform.is_jetson():
42
+ return cls._get_jetson_encoder()
43
+
44
+ # macOS
45
+ if sys.platform == "darwin":
46
+ return cls._get_videotoolbox_encoder()
47
+
48
+ # NVIDIA GPU
49
+ if cls._has_nvidia_gpu():
50
+ return cls._get_nvenc_encoder()
51
+
52
+ # Fallback to CPU
53
+ return cls._get_cpu_encoder()
54
+
55
+ @staticmethod
56
+ def _has_nvidia_gpu() -> bool:
57
+ """Check if NVIDIA GPU is available."""
58
+ return (
59
+ os.environ.get("NVIDIA_VISIBLE_DEVICES") is not None or
60
+ os.path.exists("/proc/driver/nvidia/version")
61
+ )
62
+
63
+ @staticmethod
64
+ def _get_cpu_encoder() -> Tuple[List[str], str]:
65
+ """Get CPU encoder (libx264) with optimized settings."""
66
+ return [
67
+ "-c:v", "libx264",
68
+ "-preset", "ultrafast",
69
+ "-tune", "zerolatency",
70
+ "-profile:v", "main",
71
+ ], "libx264"
72
+
73
+ @staticmethod
74
+ def _get_nvenc_encoder() -> Tuple[List[str], str]:
75
+ """Get NVIDIA NVENC encoder with GPU-optimized settings."""
76
+ return [
77
+ "-c:v", "h264_nvenc",
78
+ "-preset", "p1", # p1 = fastest preset
79
+ "-tune", "ull", # ultra-low latency
80
+ "-rc:v", "cbr", # constant bitrate
81
+ "-rc-lookahead", "0", # disable lookahead for lower latency
82
+ "-delay", "0", # zero delay
83
+ "-zerolatency", "1", # zero latency mode
84
+ "-profile:v", "main",
85
+ "-gpu", "0", # Use first GPU
86
+ ], "h264_nvenc"
87
+
88
+ @staticmethod
89
+ def _get_jetson_encoder() -> Tuple[List[str], str]:
90
+ """Get Jetson-optimized NVENC encoder."""
91
+ return [
92
+ "-c:v", "h264_nvenc",
93
+ "-preset", "p1",
94
+ "-tune", "ull",
95
+ "-rc:v", "cbr",
96
+ "-rc-lookahead", "0",
97
+ "-delay", "0",
98
+ "-zerolatency", "1",
99
+ "-profile:v", "main",
100
+ ], "h264_nvenc"
101
+
102
+ @staticmethod
103
+ def _get_videotoolbox_encoder() -> Tuple[List[str], str]:
104
+ """Get macOS VideoToolbox encoder."""
105
+ return [
106
+ "-c:v", "h264_videotoolbox",
107
+ "-profile:v", "main",
108
+ "-realtime", "1",
109
+ ], "h264_videotoolbox"
@@ -138,7 +138,7 @@ class DataSyncWorker:
138
138
 
139
139
  if not response or not response.get("success"):
140
140
  error_message = GrpcClientBase.get_error_message(response)
141
- logger.error(f"❌ [APP] Failed to sync dataset sources: {error_message}")
141
+ logger.error(f"❌ [APP] Failed to sync worker source pipelines detection: {error_message}")
142
142
 
143
143
  except Exception as e:
144
- logger.error("🚨 [APP] Error syncing worker source pipelines detection.", exc_info=True)
144
+ logger.error(f"🚨 [APP] Error syncing worker source pipelines detection: {e}", exc_info=True)
@@ -284,7 +284,7 @@ class DatasetFrameWorker:
284
284
  self.last_sync_time = time.time()
285
285
  else:
286
286
  error_message = response.get("message", "Unknown error") if response else "Unknown error"
287
- logger.error(f"❌ [APP] Failed to sync dataset sources: {error_message}")
287
+ logger.error(f"❌ [APP] Failed to sync dataset sources: {error_message}", exc_info=True)
288
288
 
289
289
  except Exception as e:
290
290
  logger.error("🚨 [APP] Error syncing dataset sources.", exc_info=True)
@@ -151,6 +151,16 @@ class WorkerService:
151
151
  self.logger.info("✅ [APP] Configuration updated successfully")
152
152
  else:
153
153
  self.logger.info("✅ [APP] Configuration found. No changes needed.")
154
+
155
+ # Always fetch connection info on startup to check for updates
156
+ self.logger.info("🔄 [APP] Checking for connection info updates...")
157
+ token_to_use = self.token if self.token else config.get('token')
158
+ if token_to_use:
159
+ AppInitializer.update_connection_info(server_host, self.server_port, token_to_use)
160
+ # Reload config after potential updates
161
+ config = ConfigurationManager.get_all_configs()
162
+ else:
163
+ self.logger.warning("⚠️ [APP] No token available to fetch connection info updates")
154
164
 
155
165
  # Add runtime parameters to config
156
166
  config['rtmp_server'] = self.rtmp_server
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nedo-vision-worker
3
- Version: 1.2.7
3
+ Version: 1.2.9
4
4
  Summary: Nedo Vision Worker Service 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>
@@ -1,12 +1,12 @@
1
- nedo_vision_worker/__init__.py,sha256=1QtTEMaMlbF3PH-GmXuZzF1PvxRe_CUCme72WXqFci0,203
1
+ nedo_vision_worker/__init__.py,sha256=oLzUhGDH-bezYa_Smc3pWfjWYzwQGUZ0LiTwVuu5AYA,203
2
2
  nedo_vision_worker/cli.py,sha256=ddWspJmSgVkcUYvRdkvTtMNuMTDvNCqLLuMVU9KE3Ik,7457
3
3
  nedo_vision_worker/doctor.py,sha256=wNkpe8gLVd76Y_ViyK2h1ZFdqeSl37MnzZN5frWKu30,48410
4
- nedo_vision_worker/worker_service.py,sha256=rXUVmyxcJPGhQEZ4UQvjQS5UqlnLBYudHQZCj0dQDxo,10421
4
+ nedo_vision_worker/worker_service.py,sha256=9zz8hKwDwqwpfS0KPQfftGJtRci0uj_wiwcr_TGf-E0,11039
5
5
  nedo_vision_worker/config/ConfigurationManager.py,sha256=QrQaQ9Cdjpkcr2JE_miyrWJIZmMgZwJYBz-wE45Zzes,8011
6
6
  nedo_vision_worker/config/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
7
7
  nedo_vision_worker/database/DatabaseManager.py,sha256=j2koXo1fnMmAyQnY4sv4txfZR8qIzrPyev-sQ4HBaOQ,9478
8
8
  nedo_vision_worker/database/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
9
- nedo_vision_worker/initializer/AppInitializer.py,sha256=iGw8-7Eg2aNT-nFaxiRIhWHU8iwecLuwuvjWlt1-V0Y,3163
9
+ nedo_vision_worker/initializer/AppInitializer.py,sha256=6UVdjiuayziPYZ7JkQ436z7-9sHj7J3jtp6lfQsu-DU,5698
10
10
  nedo_vision_worker/initializer/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
11
11
  nedo_vision_worker/models/__init__.py,sha256=6ZH2W1Jcy4o6xBPqFPcyxRU2UJ5Zvw_kfO38yLLGtHA,796
12
12
  nedo_vision_worker/models/ai_model.py,sha256=9muyZL9AxtX417-tYUiw8bgvFPtqdXgEAq-hm_mLxGY,2277
@@ -40,8 +40,8 @@ nedo_vision_worker/protos/WorkerSourceService_pb2_grpc.py,sha256=23Zj3WSogfhmp_Y
40
40
  nedo_vision_worker/protos/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
41
41
  nedo_vision_worker/repositories/AIModelRepository.py,sha256=35cQ2EnJvMdArewumPrVIaiwueGy6cXU_g5J1Qcj6_Y,1415
42
42
  nedo_vision_worker/repositories/DatasetSourceRepository.py,sha256=MOUbXY1-7WUPhY15aMpdXl6rl0afSvu3Cq8jNtp9nzM,8595
43
- nedo_vision_worker/repositories/PPEDetectionRepository.py,sha256=oyQjIRyfa0pA-ErVtrKkCLMY521QIbz9ZqSXotC8nqY,4766
44
- nedo_vision_worker/repositories/RestrictedAreaRepository.py,sha256=y3n2ZfQbth1I_IjVYaT38qpRINQQC_dKeTf0yHPFNjw,3724
43
+ nedo_vision_worker/repositories/PPEDetectionRepository.py,sha256=s4iCaTNYoJJgGr9ojWgoj6wtUQYFbloxSpnuntX26yw,5792
44
+ nedo_vision_worker/repositories/RestrictedAreaRepository.py,sha256=XudFBscLCXCheUTAPC_CbwBkJMn2uIx2HCnU0NH4riE,4766
45
45
  nedo_vision_worker/repositories/WorkerSourcePipelineDebugRepository.py,sha256=kOlVEnPOoDRZdZIm8uWXlc89GMvBPI-36QyKecX7ucE,3350
46
46
  nedo_vision_worker/repositories/WorkerSourcePipelineDetectionRepository.py,sha256=cbgg_7p0eNUIgCHoPDZBaRZ1b2Y68p_dfSxpvuGMtRE,1773
47
47
  nedo_vision_worker/repositories/WorkerSourcePipelineRepository.py,sha256=xfmEvgnyt-DdfSApGyFfy0H0dXjFFkjeo4LMr0fVFXU,10053
@@ -50,15 +50,15 @@ nedo_vision_worker/repositories/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKr
50
50
  nedo_vision_worker/services/AIModelClient.py,sha256=lxRNax6FR-pV0G1NpJnlaqjbQeu3kRolIUNSw1RkoZA,15406
51
51
  nedo_vision_worker/services/ConnectionInfoClient.py,sha256=toC9zuY2Hrx1Cwq8Gycy_iFlaG1DvFT4qewlLlitpEQ,2214
52
52
  nedo_vision_worker/services/DatasetSourceClient.py,sha256=O5a7onxFl0z47zXaMXWxHAMPuuc-i_vzkd2w5fwrukc,3319
53
- nedo_vision_worker/services/DirectDeviceToRTMPStreamer.py,sha256=M5ei0cd3_KDhHZp6EkrOowhAY-hAHfAQh9YDVjQtbQI,22278
54
- nedo_vision_worker/services/FileToRTMPServer.py,sha256=yUJxrouoTLSq9XZ88dhDYhP-px10jLoHopkPoy4lQxk,2663
53
+ nedo_vision_worker/services/DirectDeviceToRTMPStreamer.py,sha256=K0n7iyR7jdce8IWdNuqa1Im-R00QnxBcb7u2KM2Wjbc,22423
54
+ nedo_vision_worker/services/FileToRTMPServer.py,sha256=0hY5pmeAzLw_d3uPR2Qp6gSAYb4rJHiAunuNe08OvkM,2870
55
55
  nedo_vision_worker/services/GrpcClientBase.py,sha256=hPyxOGw3aGSW1FhmY3wp3Iq8U1MArXBmvEMdmd63NZ4,6827
56
56
  nedo_vision_worker/services/GrpcClientManager.py,sha256=DLXekmxlQogLo8V9-TNDXtyHT_UG-BaggqwsIups55k,5568
57
57
  nedo_vision_worker/services/GrpcConnection.py,sha256=UNjaUC4ZcXuteHQx8AAAL5ymYkT1OpoIvyCYPUc3tCI,4915
58
58
  nedo_vision_worker/services/ImageUploadClient.py,sha256=T353YsRfm74G7Mh-eWr5nvdQHXTfpKwHJFmNW8HyjT8,3019
59
- nedo_vision_worker/services/PPEDetectionClient.py,sha256=CC-b0LRAgrftfIKp6TFKpeBkTYefe-C6Z1oz_X3HArQ,4345
60
- nedo_vision_worker/services/RTSPtoRTMPStreamer.py,sha256=hkPRX6iGxUtivsOA36JWBopHEd1RlmgIP8SLqN4d2TU,3863
61
- nedo_vision_worker/services/RestrictedAreaClient.py,sha256=AE9SOcVQca4zn5V96boho56EgN5BCIpV-8grvFBBnGo,3853
59
+ nedo_vision_worker/services/PPEDetectionClient.py,sha256=3oE_Y0Avw_kcWKgTA7qmChq32woW7JerWefrCxllUYU,5172
60
+ nedo_vision_worker/services/RTSPtoRTMPStreamer.py,sha256=LtfrWDHNcm-Ky6nZLnFCF8xgqIm7VQmsWIenK2yKNfo,3804
61
+ nedo_vision_worker/services/RestrictedAreaClient.py,sha256=TD2Y5UJ0NjXgNFNgwS5ze4P-5jWwgrP0j0Nlyyd7-6Q,4844
62
62
  nedo_vision_worker/services/SharedDirectDeviceClient.py,sha256=dylMhqpMsfK_UKLWIVL-ApJRP4g-NCP_55xvlGYBiwo,10760
63
63
  nedo_vision_worker/services/SharedVideoStreamServer.py,sha256=WMKVxkzMoyfbgYiJ0fQOT-Ujz9btz6FLlaDP738yfoY,11601
64
64
  nedo_vision_worker/services/SystemUsageClient.py,sha256=Yf77dooQeNh6CDL5FkWVrX9543OVz1wc3exCAg6GlWw,3273
@@ -66,10 +66,11 @@ nedo_vision_worker/services/SystemWideDeviceCoordinator.py,sha256=9zBJMCbTMZS7gw
66
66
  nedo_vision_worker/services/VideoSharingDaemon.py,sha256=hYMjUIKNUVT1qSxuUuHN-7Bd85MDkxfqslxDLe2PBYQ,29721
67
67
  nedo_vision_worker/services/VideoStreamClient.py,sha256=QSgUV3LijYrNdnBG1ylABOdUaSatQamfXaqJhAiol9M,7260
68
68
  nedo_vision_worker/services/WorkerSourceClient.py,sha256=vDZeCuHL5QQ2-knZ4TOSA59jzmbbThGIwFKKLEZ72Ws,9198
69
- nedo_vision_worker/services/WorkerSourcePipelineClient.py,sha256=qaBx9T2gWMzpqZaeQdbIeklsXNwzWD5kqgB41rrSkBI,17135
69
+ nedo_vision_worker/services/WorkerSourcePipelineClient.py,sha256=cjev_NRGUZrC9tuMm8s5ov92fknQno4vLEt-yMFrUCY,18241
70
70
  nedo_vision_worker/services/WorkerSourceUpdater.py,sha256=4t_CEHBLGDRvvuQS6eEPMivTI11ZuzusKKto6t9tPIk,9115
71
71
  nedo_vision_worker/services/WorkerStatusClient.py,sha256=7kC5EZjEBwWtHOE6UQ29OPCpYnv_6HSuH7Tc0alK_2Q,2531
72
72
  nedo_vision_worker/services/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
73
+ nedo_vision_worker/util/EncoderSelector.py,sha256=-9lZwVmiKzJr1cELeuCXi-jRonty2bpociZq4KDScmA,3399
73
74
  nedo_vision_worker/util/FFmpegUtil.py,sha256=QnQrzurmllzGb7SlAAYCrzKBUblweoFU-0h-X-32IYg,1829
74
75
  nedo_vision_worker/util/HardwareID.py,sha256=rSW8-6stm7rjXEdkYGqXMUn56gyw62YiWnSwZQVCCLM,4315
75
76
  nedo_vision_worker/util/ImageUploader.py,sha256=2xipN3fwpKgFmbvoGIdElpGn5ARJyrgR4dXtbRf73hw,3764
@@ -80,9 +81,9 @@ nedo_vision_worker/util/VideoProbeUtil.py,sha256=cF-vJ7hIDlXfEJby2a0s9tqwkPGVz_6
80
81
  nedo_vision_worker/util/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
81
82
  nedo_vision_worker/worker/CoreActionWorker.py,sha256=lb7zPY3yui6I3F4rX4Ii7JwpWZahLEO72rh3iWOgFmg,5441
82
83
  nedo_vision_worker/worker/DataSenderWorker.py,sha256=9FudRRItiMOcQx5UfVyu4p0Enb9BbgwZZ5EgX6Ho2U4,7160
83
- nedo_vision_worker/worker/DataSyncWorker.py,sha256=WvYfi3bG4mOKHU09J_MavfjFPrVgmxrrZYtrlQ-bnio,6265
84
+ nedo_vision_worker/worker/DataSyncWorker.py,sha256=LmDPt2J1frmXwuR46L6b0MjlFOHfgG-4_0MGQa78zF4,6288
84
85
  nedo_vision_worker/worker/DatasetFrameSender.py,sha256=1SFYj8LJFNi-anBTapsbq8U_NGMM7mnoMKg9NeFAHys,8087
85
- nedo_vision_worker/worker/DatasetFrameWorker.py,sha256=Ni5gPeDPk9Rz4_cbg63u7Y6LVw_-Bz24OvfeY-6Yp44,19320
86
+ nedo_vision_worker/worker/DatasetFrameWorker.py,sha256=Hh_wZuMjwovxsEKFqXSuTRin9eYRBZCbcFKm3CKLMbE,19335
86
87
  nedo_vision_worker/worker/PPEDetectionManager.py,sha256=fAolWlrsY5SQAWygvjNBNU56IlC0HLlhPgpz7shL-gk,3588
87
88
  nedo_vision_worker/worker/PipelineActionWorker.py,sha256=xgvryjKtEsMj4BKqWzDIaK_lFny-DfMCj5Y2DxHnWww,5651
88
89
  nedo_vision_worker/worker/PipelineImageWorker.py,sha256=J8VBUG0cwcH3qOJp2zTl30B-XhmPFyvJLjxitKJYq0E,5642
@@ -93,8 +94,8 @@ nedo_vision_worker/worker/SystemUsageManager.py,sha256=mkh4sT-HkIEY1CJHMEG6LP9AT
93
94
  nedo_vision_worker/worker/VideoStreamWorker.py,sha256=5n6v1PNO7IB-jj_McALLkUP-cBjJoIEw4UiSAs3vTb0,7606
94
95
  nedo_vision_worker/worker/WorkerManager.py,sha256=2bxXi19fp3p1qjYBStYRdVVgko8dnevXx1_M_sqH5og,5521
95
96
  nedo_vision_worker/worker/__init__.py,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
96
- nedo_vision_worker-1.2.7.dist-info/METADATA,sha256=xcM1OD1cAy83Rz4OaFYnHsHO8-DgNBrP0zEX9-_uX3I,14728
97
- nedo_vision_worker-1.2.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
98
- nedo_vision_worker-1.2.7.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
99
- nedo_vision_worker-1.2.7.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
100
- nedo_vision_worker-1.2.7.dist-info/RECORD,,
97
+ nedo_vision_worker-1.2.9.dist-info/METADATA,sha256=ySyI3I8lzUmhonotTgOrhH2Tt-AxL_l9oYifd_qaHcU,14728
98
+ nedo_vision_worker-1.2.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
99
+ nedo_vision_worker-1.2.9.dist-info/entry_points.txt,sha256=LrglS-8nCi8C_PL_pa6uxdgCe879hBETHDVXAckvs-8,60
100
+ nedo_vision_worker-1.2.9.dist-info/top_level.txt,sha256=vgilhlkyD34YsEKkaBabmhIpcKSvF3XpzD2By68L-XI,19
101
+ nedo_vision_worker-1.2.9.dist-info/RECORD,,