nedo-vision-worker 1.1.2__py3-none-any.whl → 1.2.0__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.
- nedo_vision_worker/__init__.py +1 -1
- nedo_vision_worker/cli.py +197 -168
- nedo_vision_worker/database/DatabaseManager.py +3 -3
- nedo_vision_worker/doctor.py +1066 -386
- nedo_vision_worker/models/ai_model.py +35 -2
- nedo_vision_worker/protos/AIModelService_pb2.py +12 -10
- nedo_vision_worker/protos/AIModelService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/DatasetSourceService_pb2.py +2 -2
- nedo_vision_worker/protos/DatasetSourceService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/HumanDetectionService_pb2.py +2 -2
- nedo_vision_worker/protos/HumanDetectionService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/PPEDetectionService_pb2.py +2 -2
- nedo_vision_worker/protos/PPEDetectionService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/VisionWorkerService_pb2.py +2 -2
- nedo_vision_worker/protos/VisionWorkerService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/WorkerSourcePipelineService_pb2.py +2 -2
- nedo_vision_worker/protos/WorkerSourcePipelineService_pb2_grpc.py +1 -1
- nedo_vision_worker/protos/WorkerSourceService_pb2.py +2 -2
- nedo_vision_worker/protos/WorkerSourceService_pb2_grpc.py +1 -1
- nedo_vision_worker/services/AIModelClient.py +184 -160
- nedo_vision_worker/services/DirectDeviceToRTMPStreamer.py +534 -0
- nedo_vision_worker/services/GrpcClientBase.py +142 -108
- nedo_vision_worker/services/PPEDetectionClient.py +0 -7
- nedo_vision_worker/services/RestrictedAreaClient.py +0 -5
- nedo_vision_worker/services/SharedDirectDeviceClient.py +278 -0
- nedo_vision_worker/services/SharedVideoStreamServer.py +315 -0
- nedo_vision_worker/services/SystemWideDeviceCoordinator.py +236 -0
- nedo_vision_worker/services/VideoSharingDaemon.py +832 -0
- nedo_vision_worker/services/VideoStreamClient.py +30 -13
- nedo_vision_worker/services/WorkerSourceClient.py +1 -1
- nedo_vision_worker/services/WorkerSourcePipelineClient.py +28 -6
- nedo_vision_worker/services/WorkerSourceUpdater.py +30 -3
- nedo_vision_worker/util/VideoProbeUtil.py +222 -15
- nedo_vision_worker/worker/DataSyncWorker.py +1 -0
- nedo_vision_worker/worker/PipelineImageWorker.py +1 -1
- nedo_vision_worker/worker/VideoStreamWorker.py +27 -3
- nedo_vision_worker/worker/WorkerManager.py +2 -29
- nedo_vision_worker/worker_service.py +24 -11
- {nedo_vision_worker-1.1.2.dist-info → nedo_vision_worker-1.2.0.dist-info}/METADATA +1 -3
- {nedo_vision_worker-1.1.2.dist-info → nedo_vision_worker-1.2.0.dist-info}/RECORD +43 -38
- {nedo_vision_worker-1.1.2.dist-info → nedo_vision_worker-1.2.0.dist-info}/WHEEL +0 -0
- {nedo_vision_worker-1.1.2.dist-info → nedo_vision_worker-1.2.0.dist-info}/entry_points.txt +0 -0
- {nedo_vision_worker-1.1.2.dist-info → nedo_vision_worker-1.2.0.dist-info}/top_level.txt +0 -0
nedo_vision_worker/__init__.py
CHANGED
nedo_vision_worker/cli.py
CHANGED
|
@@ -3,193 +3,222 @@ import signal
|
|
|
3
3
|
import sys
|
|
4
4
|
import traceback
|
|
5
5
|
import logging
|
|
6
|
+
from typing import NoReturn
|
|
6
7
|
|
|
7
8
|
from .worker_service import WorkerService
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
class NedoWorkerCLI:
|
|
12
|
+
"""Main CLI application for Nedo Vision Worker Service."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.logger = logging.getLogger(__name__)
|
|
16
|
+
self._setup_signal_handlers()
|
|
17
|
+
|
|
18
|
+
def _setup_signal_handlers(self) -> None:
|
|
19
|
+
"""Set up signal handlers for graceful shutdown."""
|
|
20
|
+
signal.signal(signal.SIGINT, self._signal_handler)
|
|
21
|
+
signal.signal(signal.SIGTERM, self._signal_handler)
|
|
22
|
+
|
|
23
|
+
def _signal_handler(self, signum: int, frame) -> NoReturn:
|
|
24
|
+
"""Handle system signals for graceful shutdown."""
|
|
25
|
+
self.logger.info(f"Received signal {signum}, shutting down...")
|
|
26
|
+
sys.exit(0)
|
|
27
|
+
|
|
28
|
+
def create_parser(self) -> argparse.ArgumentParser:
|
|
29
|
+
"""Create and configure the argument parser."""
|
|
30
|
+
parser = argparse.ArgumentParser(
|
|
31
|
+
description="Nedo Vision Worker Service",
|
|
32
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
33
|
+
epilog="""
|
|
22
34
|
Examples:
|
|
23
35
|
# Check system dependencies and requirements
|
|
24
36
|
nedo-worker doctor
|
|
25
37
|
|
|
26
|
-
# Start worker service
|
|
27
|
-
nedo-worker run --token your-token-here
|
|
28
|
-
|
|
29
|
-
# Start with custom server host
|
|
30
|
-
nedo-worker run --token your-token-here --rtmp-server rtmp://server.com:1935/live --server-host custom.server.com
|
|
31
|
-
|
|
32
|
-
# Start with custom storage path
|
|
33
|
-
nedo-worker run --token your-token-here --rtmp-server rtmp://server.com:1935/live --storage-path /path/to/storage
|
|
34
|
-
"""
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
# Add subcommands
|
|
38
|
-
subparsers = parser.add_subparsers(dest='command', help='Available commands')
|
|
39
|
-
|
|
40
|
-
# Doctor command
|
|
41
|
-
doctor_parser = subparsers.add_parser(
|
|
42
|
-
'doctor',
|
|
43
|
-
help='Check system dependencies and requirements',
|
|
44
|
-
description='Run diagnostic checks for FFmpeg, OpenCV, gRPC and other dependencies'
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
# Run command
|
|
48
|
-
run_parser = subparsers.add_parser(
|
|
49
|
-
'run',
|
|
50
|
-
help='Start the worker service',
|
|
51
|
-
description='Start the Nedo Vision Worker Service'
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
run_parser.add_argument(
|
|
55
|
-
"--server-host",
|
|
56
|
-
default="be.vision.sindika.co.id",
|
|
57
|
-
help="Server hostname for communication (default: be.vision.sindika.co.id)"
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
run_parser.add_argument(
|
|
61
|
-
"--token",
|
|
62
|
-
required=True,
|
|
63
|
-
help="Authentication token for the worker (obtained from frontend)"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
run_parser.add_argument(
|
|
67
|
-
"--system-usage-interval",
|
|
68
|
-
type=int,
|
|
69
|
-
default=30,
|
|
70
|
-
help="System usage reporting interval in seconds (default: 30)"
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
run_parser.add_argument(
|
|
74
|
-
"--rtmp-server",
|
|
75
|
-
required=True,
|
|
76
|
-
help="RTMP server URL for video streaming (e.g., rtmp://server.com:1935/live)"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
run_parser.add_argument(
|
|
80
|
-
"--storage-path",
|
|
81
|
-
default="data",
|
|
82
|
-
help="Storage path for databases and files (default: data)"
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
# Add legacy arguments for backward compatibility (when no subcommand is used)
|
|
86
|
-
parser.add_argument(
|
|
87
|
-
"--token",
|
|
88
|
-
help="(Legacy) Authentication token for the worker (obtained from frontend)"
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
parser.add_argument(
|
|
92
|
-
"--server-host",
|
|
93
|
-
default="be.vision.sindika.co.id",
|
|
94
|
-
help="(Legacy) Server hostname for communication (default: be.vision.sindika.co.id)"
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
parser.add_argument(
|
|
98
|
-
"--system-usage-interval",
|
|
99
|
-
type=int,
|
|
100
|
-
default=30,
|
|
101
|
-
help="(Legacy) System usage reporting interval in seconds (default: 30)"
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
parser.add_argument(
|
|
105
|
-
"--rtmp-server",
|
|
106
|
-
help="(Legacy) RTMP server URL for video streaming (e.g., rtmp://server.com:1935/live)"
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
parser.add_argument(
|
|
110
|
-
"--storage-path",
|
|
111
|
-
default="data",
|
|
112
|
-
help="(Legacy) Storage path for databases and files (default: data)"
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
parser.add_argument(
|
|
116
|
-
"--version",
|
|
117
|
-
action="version",
|
|
118
|
-
version="nedo-vision-worker 1.1.2"
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
parser.add_argument(
|
|
122
|
-
"--doctor",
|
|
123
|
-
action="store_true",
|
|
124
|
-
help="(Deprecated) Run system diagnostics - use 'nedo-worker doctor' instead"
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
args = parser.parse_args()
|
|
128
|
-
|
|
129
|
-
# Handle subcommands
|
|
130
|
-
if args.command == 'doctor':
|
|
131
|
-
from .doctor import main as doctor_main
|
|
132
|
-
sys.exit(doctor_main())
|
|
133
|
-
elif args.command == 'run':
|
|
134
|
-
run_worker_service(args)
|
|
135
|
-
elif args.doctor: # Legacy mode - deprecated --doctor flag
|
|
136
|
-
print("⚠️ Warning: Using deprecated --doctor flag. Use 'nedo-worker doctor' instead.")
|
|
137
|
-
from .doctor import main as doctor_main
|
|
138
|
-
sys.exit(doctor_main())
|
|
139
|
-
elif args.token and args.rtmp_server: # Legacy mode - if token and rtmp_server are provided without subcommand
|
|
140
|
-
print("⚠️ Warning: Using legacy command format. Consider using 'nedo-worker run --token ... --rtmp-server ...' instead.")
|
|
141
|
-
run_worker_service(args)
|
|
142
|
-
else:
|
|
143
|
-
# If no subcommand provided and no token, show help
|
|
144
|
-
parser.print_help()
|
|
145
|
-
sys.exit(1)
|
|
146
|
-
|
|
38
|
+
# Start worker service
|
|
39
|
+
nedo-worker run --token your-token-here
|
|
147
40
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
41
|
+
# Start with custom configuration
|
|
42
|
+
nedo-worker run --token your-token-here \\
|
|
43
|
+
--rtmp-server rtmp://custom.server.com:1935/live \\
|
|
44
|
+
--server-host custom.server.com \\
|
|
45
|
+
--storage-path /custom/storage/path
|
|
46
|
+
"""
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--version",
|
|
51
|
+
action="version",
|
|
52
|
+
version="nedo-vision-worker 1.2.0"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
subparsers = parser.add_subparsers(
|
|
56
|
+
dest='command',
|
|
57
|
+
help='Available commands',
|
|
58
|
+
required=True
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
self._add_doctor_command(subparsers)
|
|
62
|
+
self._add_run_command(subparsers)
|
|
63
|
+
|
|
64
|
+
return parser
|
|
65
|
+
|
|
66
|
+
def _add_doctor_command(self, subparsers) -> None:
|
|
67
|
+
"""Add the doctor command."""
|
|
68
|
+
subparsers.add_parser(
|
|
69
|
+
'doctor',
|
|
70
|
+
help='Check system dependencies and requirements',
|
|
71
|
+
description='Run diagnostic checks for FFmpeg, OpenCV, gRPC and other dependencies'
|
|
72
|
+
)
|
|
155
73
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
74
|
+
def _add_run_command(self, subparsers) -> None:
|
|
75
|
+
"""Add the run command with its arguments."""
|
|
76
|
+
run_parser = subparsers.add_parser(
|
|
77
|
+
'run',
|
|
78
|
+
help='Start the worker service',
|
|
79
|
+
description='Start the Nedo Vision Worker Service'
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
run_parser.add_argument(
|
|
83
|
+
"--token",
|
|
84
|
+
required=True,
|
|
85
|
+
help="Authentication token for the worker (obtained from frontend)"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
run_parser.add_argument(
|
|
89
|
+
"--server-host",
|
|
90
|
+
default="be.vision.sindika.co.id",
|
|
91
|
+
help="Server hostname for communication (default: %(default)s)"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
run_parser.add_argument(
|
|
95
|
+
"--server-port",
|
|
96
|
+
type=int,
|
|
97
|
+
default=50051,
|
|
98
|
+
help="Server port for gRPC communication (default: %(default)s)"
|
|
164
99
|
)
|
|
165
100
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
101
|
+
run_parser.add_argument(
|
|
102
|
+
"--rtmp-server",
|
|
103
|
+
default="rtmp://live.vision.sindika.co.id:1935/live",
|
|
104
|
+
help="RTMP server URL for video streaming (default: %(default)s)"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
run_parser.add_argument(
|
|
108
|
+
"--storage-path",
|
|
109
|
+
default="data",
|
|
110
|
+
help="Storage path for databases and files (default: %(default)s)"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
run_parser.add_argument(
|
|
114
|
+
"--system-usage-interval",
|
|
115
|
+
type=int,
|
|
116
|
+
default=30,
|
|
117
|
+
metavar="SECONDS",
|
|
118
|
+
help="System usage reporting interval in seconds (default: %(default)s)"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
run_parser.add_argument(
|
|
122
|
+
"--log-level",
|
|
123
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
|
|
124
|
+
default="INFO",
|
|
125
|
+
help="Set the logging level (default: %(default)s)"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def run_doctor_command(self) -> int:
|
|
129
|
+
"""Execute the doctor command."""
|
|
130
|
+
try:
|
|
131
|
+
from .doctor import main as doctor_main
|
|
132
|
+
return doctor_main()
|
|
133
|
+
except ImportError as e:
|
|
134
|
+
self.logger.error(f"Failed to import doctor module: {e}")
|
|
135
|
+
return 1
|
|
136
|
+
except Exception as e:
|
|
137
|
+
self.logger.error(f"Doctor command failed: {e}")
|
|
138
|
+
return 1
|
|
139
|
+
|
|
140
|
+
def run_worker_service(self, args: argparse.Namespace) -> int:
|
|
141
|
+
"""Start and run the worker service."""
|
|
142
|
+
# Configure logging
|
|
143
|
+
logging.basicConfig(
|
|
144
|
+
level=getattr(logging, args.log_level),
|
|
145
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
146
|
+
)
|
|
173
147
|
|
|
174
|
-
|
|
175
|
-
|
|
148
|
+
try:
|
|
149
|
+
# Create the worker service
|
|
150
|
+
service = WorkerService(
|
|
151
|
+
server_host=args.server_host,
|
|
152
|
+
token=args.token,
|
|
153
|
+
system_usage_interval=args.system_usage_interval,
|
|
154
|
+
rtmp_server=args.rtmp_server,
|
|
155
|
+
storage_path=args.storage_path,
|
|
156
|
+
server_port=args.server_port
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# Log startup information
|
|
160
|
+
self._log_startup_info(args)
|
|
161
|
+
|
|
162
|
+
# Start the service
|
|
163
|
+
service.run()
|
|
164
|
+
|
|
165
|
+
# Keep the service running
|
|
166
|
+
self._wait_for_service(service)
|
|
167
|
+
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
except KeyboardInterrupt:
|
|
171
|
+
self.logger.info("Shutdown requested by user")
|
|
172
|
+
return 0
|
|
173
|
+
except Exception as e:
|
|
174
|
+
self.logger.error(f"Service failed: {e}")
|
|
175
|
+
if args.log_level == "DEBUG":
|
|
176
|
+
traceback.print_exc()
|
|
177
|
+
return 1
|
|
178
|
+
|
|
179
|
+
def _log_startup_info(self, args: argparse.Namespace) -> None:
|
|
180
|
+
"""Log service startup information."""
|
|
181
|
+
self.logger.info("🚀 Starting Nedo Vision Worker Service...")
|
|
182
|
+
self.logger.info(f"🌐 Server: {args.server_host}")
|
|
183
|
+
self.logger.info(f"🔑 Token: {args.token[:8]}{'*' * (len(args.token) - 8)}")
|
|
184
|
+
self.logger.info(f"⏱️ System Usage Interval: {args.system_usage_interval}s")
|
|
185
|
+
self.logger.info(f"📡 RTMP Server: {args.rtmp_server}")
|
|
186
|
+
self.logger.info(f"💾 Storage Path: {args.storage_path}")
|
|
187
|
+
self.logger.info("Press Ctrl+C to stop the service")
|
|
188
|
+
|
|
189
|
+
def _wait_for_service(self, service: WorkerService) -> None:
|
|
190
|
+
"""Wait for the service to run and handle shutdown."""
|
|
191
|
+
import time
|
|
176
192
|
|
|
177
|
-
# Keep the service running
|
|
178
193
|
try:
|
|
179
194
|
while getattr(service, 'running', False):
|
|
180
|
-
import time
|
|
181
195
|
time.sleep(1)
|
|
182
196
|
except KeyboardInterrupt:
|
|
183
|
-
logger.info("
|
|
197
|
+
self.logger.info("🛑 Shutdown requested...")
|
|
184
198
|
finally:
|
|
185
|
-
service
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
199
|
+
if hasattr(service, 'stop'):
|
|
200
|
+
service.stop()
|
|
201
|
+
self.logger.info("✅ Service stopped successfully")
|
|
202
|
+
|
|
203
|
+
def run(self) -> int:
|
|
204
|
+
"""Main entry point for the CLI application."""
|
|
205
|
+
parser = self.create_parser()
|
|
206
|
+
args = parser.parse_args()
|
|
207
|
+
|
|
208
|
+
if args.command == 'doctor':
|
|
209
|
+
return self.run_doctor_command()
|
|
210
|
+
elif args.command == 'run':
|
|
211
|
+
return self.run_worker_service(args)
|
|
212
|
+
else:
|
|
213
|
+
parser.print_help()
|
|
214
|
+
return 1
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main() -> int:
|
|
218
|
+
"""Main CLI entry point."""
|
|
219
|
+
cli = NedoWorkerCLI()
|
|
220
|
+
return cli.run()
|
|
192
221
|
|
|
193
222
|
|
|
194
223
|
if __name__ == "__main__":
|
|
195
|
-
main()
|
|
224
|
+
sys.exit(main())
|
|
@@ -20,13 +20,13 @@ _storage_path = None # Global storage path
|
|
|
20
20
|
def set_storage_path(storage_path: str):
|
|
21
21
|
"""Set the global storage path for the application."""
|
|
22
22
|
global _storage_path
|
|
23
|
-
_storage_path = Path(storage_path)
|
|
23
|
+
_storage_path = Path(storage_path).resolve() # Convert to absolute path
|
|
24
24
|
|
|
25
25
|
def _get_storage_paths():
|
|
26
26
|
"""Get storage paths using the configured storage path."""
|
|
27
27
|
global _storage_path
|
|
28
28
|
if _storage_path is None:
|
|
29
|
-
_storage_path = Path("data") # Default fallback
|
|
29
|
+
_storage_path = Path("data").resolve() # Default fallback as absolute path
|
|
30
30
|
|
|
31
31
|
return {
|
|
32
32
|
"db": _storage_path / "sqlite",
|
|
@@ -38,7 +38,7 @@ def get_storage_path(subdir: str = None) -> Path:
|
|
|
38
38
|
"""Get a storage path for a specific subdirectory."""
|
|
39
39
|
global _storage_path
|
|
40
40
|
if _storage_path is None:
|
|
41
|
-
_storage_path = Path("data") # Default fallback
|
|
41
|
+
_storage_path = Path("data").resolve() # Default fallback as absolute path
|
|
42
42
|
|
|
43
43
|
if subdir:
|
|
44
44
|
return _storage_path / subdir
|