1+ #!/usr/bin/env python3
2+ """
3+ Barcode Scanner for IP Camera GUI Client
4+ Integrates Dynamsoft Capture Vision Bundle for real-time barcode detection.
5+ """
6+
7+ import queue
8+ import threading
9+ import time
10+ from typing import List , Optional
11+ from PySide6 .QtCore import QObject , Signal , QTimer
12+
13+ from dynamsoft_capture_vision_bundle import (
14+ LicenseManager , CaptureVisionRouter , ImageSourceAdapter ,
15+ CapturedResultReceiver , EnumErrorCode , EnumPresetTemplate ,
16+ EnumCapturedResultItemType
17+ )
18+ from utils import convertMat2ImageData
19+
20+
21+ class FrameFetcher (ImageSourceAdapter ):
22+ """Custom frame fetcher for Dynamsoft Capture Vision"""
23+
24+ def __init__ (self ):
25+ super ().__init__ ()
26+ self ._has_frame = False
27+
28+ def has_next_image_to_fetch (self ) -> bool :
29+ return self ._has_frame
30+
31+ def add_frame (self , image_data ):
32+ """Add frame to the buffer"""
33+ self .add_image_to_buffer (image_data )
34+ self ._has_frame = True
35+
36+
37+ class BarcodeResultReceiver (CapturedResultReceiver ):
38+ """Custom result receiver for barcode detection results"""
39+
40+ def __init__ (self , result_queue ):
41+ super ().__init__ ()
42+ self .result_queue = result_queue
43+
44+ def on_captured_result_received (self , captured_result ):
45+ """Called when barcode detection results are available"""
46+ try :
47+ self .result_queue .put_nowait (captured_result )
48+ except queue .Full :
49+ # Drop old results if queue is full
50+ try :
51+ self .result_queue .get_nowait ()
52+ self .result_queue .put_nowait (captured_result )
53+ except queue .Empty :
54+ pass
55+
56+
57+ class BarcodeScanner (QObject ):
58+ """
59+ Barcode scanner component that processes video frames
60+ and emits barcode detection results
61+ """
62+
63+ # Signals
64+ barcode_detected = Signal (list ) # List of detected barcode results with details
65+ error_occurred = Signal (str )
66+
67+ def __init__ (self , parent = None ):
68+ super ().__init__ (parent )
69+
70+ # Scanner state
71+ self .is_initialized = False
72+ self .is_scanning = False
73+
74+ # Dynamsoft components
75+ self .cvr = None
76+ self .fetcher = None
77+ self .receiver = None
78+ self .result_queue = queue .Queue (maxsize = 10 )
79+
80+ # Latest barcode results with location information
81+ self .latest_barcodes = []
82+ self .last_detection_time = 0
83+
84+ # Initialize license
85+ self ._initialize_license ()
86+
87+ def _initialize_license (self ):
88+ """Initialize Dynamsoft license"""
89+ try :
90+ license_key = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="
91+
92+ error_code , error_msg = LicenseManager .init_license (license_key )
93+
94+ if error_code != EnumErrorCode .EC_OK and error_code != EnumErrorCode .EC_LICENSE_CACHE_USED :
95+ self .error_occurred .emit (f"License initialization failed: { error_msg } " )
96+ return
97+
98+ self .is_initialized = True
99+ self ._setup_scanner ()
100+
101+ except Exception as e :
102+ self .error_occurred .emit (f"Failed to initialize barcode scanner: { str (e )} " )
103+
104+ def _setup_scanner (self ):
105+ """Setup Dynamsoft Capture Vision components"""
106+ try :
107+ # Create capture vision router
108+ self .cvr = CaptureVisionRouter ()
109+
110+ # Create frame fetcher
111+ self .fetcher = FrameFetcher ()
112+ self .cvr .set_input (self .fetcher )
113+
114+ # Create result receiver
115+ self .receiver = BarcodeResultReceiver (self .result_queue )
116+ self .cvr .add_result_receiver (self .receiver )
117+
118+ except Exception as e :
119+ self .error_occurred .emit (f"Failed to setup barcode scanner: { str (e )} " )
120+ self .is_initialized = False
121+
122+ def start_scanning (self ):
123+ """Start barcode scanning"""
124+ if not self .is_initialized or self .is_scanning :
125+ return
126+
127+ try :
128+ # Start capturing with barcode template
129+ error_code , error_msg = self .cvr .start_capturing (
130+ EnumPresetTemplate .PT_READ_BARCODES .value
131+ )
132+
133+ if error_code != EnumErrorCode .EC_OK :
134+ self .error_occurred .emit (f"Failed to start scanning: { error_msg } " )
135+ return
136+
137+ self .is_scanning = True
138+
139+ except Exception as e :
140+ self .error_occurred .emit (f"Error starting barcode scanning: { str (e )} " )
141+
142+ def stop_scanning (self ):
143+ """Stop barcode scanning"""
144+ if not self .is_scanning :
145+ return
146+
147+ try :
148+ # Stop capturing
149+ if self .cvr :
150+ self .cvr .stop_capturing ()
151+
152+ # Clear result queue
153+ while not self .result_queue .empty ():
154+ try :
155+ self .result_queue .get_nowait ()
156+ except queue .Empty :
157+ break
158+
159+ self .is_scanning = False
160+
161+ except Exception as e :
162+ self .error_occurred .emit (f"Error stopping barcode scanning: { str (e )} " )
163+
164+ def process_frame (self , mat ):
165+ """
166+ Process a frame for barcode detection
167+
168+ Args:
169+ mat: OpenCV Mat object (BGR format)
170+ """
171+ if not self .is_scanning or not self .fetcher :
172+ return
173+
174+ try :
175+ # Convert Mat to ImageData
176+ image_data = convertMat2ImageData (mat )
177+
178+ # Add frame to scanner
179+ self .fetcher .add_frame (image_data )
180+
181+ except Exception as e :
182+ # Don't spam with frame processing errors
183+ pass
184+
185+ def get_fresh_results (self ):
186+ """Get fresh barcode detection results from the queue right now"""
187+ if not self .is_scanning :
188+ return []
189+
190+ detected_barcodes = []
191+ detected_texts = []
192+
193+ try :
194+ # Process all available results right now
195+ while not self .result_queue .empty ():
196+ try :
197+ captured_result = self .result_queue .get_nowait ()
198+ items = captured_result .get_items ()
199+
200+ for item in items :
201+ if item .get_type () == EnumCapturedResultItemType .CRIT_BARCODE :
202+ text = item .get_text ()
203+ location = item .get_location ()
204+
205+ if text and location :
206+ # Extract corner points
207+ points = []
208+ for i in range (4 ):
209+ x = int (location .points [i ].x )
210+ y = int (location .points [i ].y )
211+ points .append ((x , y ))
212+
213+ # Create detailed barcode result
214+ barcode_result = {
215+ 'text' : text ,
216+ 'points' : points ,
217+ 'timestamp' : time .time ()
218+ }
219+
220+ detected_barcodes .append (barcode_result )
221+ if text not in detected_texts :
222+ detected_texts .append (text )
223+
224+ # Clean up location object
225+ del location
226+
227+ except queue .Empty :
228+ break
229+ except Exception as e :
230+ # Continue processing other results
231+ continue
232+
233+ # Update latest results and emit if any barcodes detected
234+ if detected_barcodes :
235+ self .latest_barcodes = detected_barcodes
236+ self .last_detection_time = time .time ()
237+ # Emit signal for text results panel
238+ self .barcode_detected .emit (detected_texts )
239+
240+ # Return fresh results for immediate rendering
241+ return detected_barcodes
242+
243+ except Exception as e :
244+ # Don't spam with result processing errors
245+ return []
246+
247+ def get_latest_barcodes (self ):
248+ """Get the cached barcode detection results (for backward compatibility)"""
249+ return self .latest_barcodes .copy () if self .latest_barcodes else []
250+
251+ def is_available (self ) -> bool :
252+ """Check if barcode scanning is available"""
253+ return self .is_initialized
254+
255+ def cleanup (self ):
256+ """Cleanup resources"""
257+ try :
258+ self .stop_scanning ()
259+
260+ if self .cvr :
261+ self .cvr = None
262+ if self .fetcher :
263+ self .fetcher = None
264+ if self .receiver :
265+ self .receiver = None
266+
267+ except Exception :
268+ pass
0 commit comments