From 3aa19b10d118b02eb3cb141eef4992753fdd1884 Mon Sep 17 00:00:00 2001 From: Aiirondev Date: Sat, 6 Jun 2026 15:03:43 +0200 Subject: [PATCH] Reverted Faulti changes that have proven not worth implementing --- Web/app.py | 60 ------ Web/requirements.txt | 1 - Web/static/js/scanner.js | 353 -------------------------------- Web/templates/library.html | 326 ++--------------------------- Web/templates/upload_admin.html | 94 +++------ 5 files changed, 45 insertions(+), 789 deletions(-) delete mode 100644 Web/static/js/scanner.js diff --git a/Web/app.py b/Web/app.py index e77e16b..4c733a4 100755 --- a/Web/app.py +++ b/Web/app.py @@ -3484,66 +3484,6 @@ def api_library_scan_action(): client.close() -@app.route('/api/scan_upload', methods=['POST']) -def api_scan_upload(): - """ - Server-side barcode/QR decoding endpoint. - Accepts image upload and returns detected barcodes (fallback for client-side scanner). - """ - if 'username' not in session: - return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401 - - try: - from pyzbar.pyzbar import decode - from PIL import Image - import io - except ImportError: - return jsonify({'ok': False, 'message': 'pyzbar library not installed on server.'}), 500 - - if 'image' not in request.files: - return jsonify({'ok': False, 'message': 'Keine Bilddatei bereitgestellt.'}), 400 - - file = request.files['image'] - if file.filename == '': - return jsonify({'ok': False, 'message': 'Datei ist leer.'}), 400 - - try: - # Decode image - image = Image.open(io.BytesIO(file.read())) - image = image.convert('RGB') - - # Detect barcodes using pyzbar - results = decode(image) - - if not results: - return jsonify({'ok': False, 'message': 'Kein Barcode im Bild erkannt.'}), 400 - - # Return all detected barcodes, sorted by confidence (quality) - barcodes = [ - { - 'type': result.type, - 'value': result.data.decode('utf-8'), - 'rect': { - 'x': result.rect.left, - 'y': result.rect.top, - 'width': result.rect.width, - 'height': result.rect.height, - } - } - for result in results - ] - - return jsonify({ - 'ok': True, - 'barcodes': barcodes, - 'message': f'{len(barcodes)} Barcode(s) erkannt.' - }), 200 - - except Exception as e: - app.logger.error(f"Error decoding barcode image: {e}") - return jsonify({'ok': False, 'message': f'Fehler beim Dekodieren: {str(e)}'}), 500 - - @app.route('/api/item_detail/') def api_item_detail(item_id): """ diff --git a/Web/requirements.txt b/Web/requirements.txt index dd42e1e..56fb992 100755 --- a/Web/requirements.txt +++ b/Web/requirements.txt @@ -15,4 +15,3 @@ openpyxl cryptography>=42.0.0 pywebpush py-vapid>=1.9.0 -pyzbar diff --git a/Web/static/js/scanner.js b/Web/static/js/scanner.js deleted file mode 100644 index 06e73fc..0000000 --- a/Web/static/js/scanner.js +++ /dev/null @@ -1,353 +0,0 @@ -/** - * Hybrid Barcode Scanner - * Supports: Real-time camera scanning (ZXing.js), image upload fallback (server-side pyzbar), - * and USB/keyboard scanner emulation. - */ - -class HybridScanner { - constructor(options = {}) { - this.options = { - videoId: options.videoId || 'scanner-video', - canvasId: options.canvasId || 'scanner-canvas', - formats: options.formats || ['QR_CODE', 'CODE_128', 'EAN_13', 'ISBN', 'CODE_39', 'UPC_A'], - fps: options.fps || 10, - onSuccess: options.onSuccess || (() => {}), - onError: options.onError || (() => {}), - facingMode: options.facingMode || 'environment', // 'environment' for rear, 'user' for front - }; - - this.state = { - isRunning: false, - stream: null, - reader: null, - canvas: null, - videoElement: null, - zxingReady: false, - lastScanTime: 0, - scanDebounceMs: 300, // Prevent rapid duplicate scans - }; - - this.keyboardInputBuffer = ''; - this.keyboardInputTimeout = null; - } - - /** - * Initialize ZXing library from CDN - */ - async initZXing() { - if (this.state.zxingReady) { - return true; - } - - return new Promise((resolve) => { - // Load ZXing library from CDN - if (typeof ZXing !== 'undefined') { - this.state.zxingReady = true; - resolve(true); - return; - } - - // Use fallback URL for better mobile compatibility - let scriptUrl = 'https://unpkg.com/@zxing/library@latest/umd/index.min.js'; - - // Check if already loading - const existingScript = document.querySelector(`script[src="${scriptUrl}"]`); - if (existingScript) { - existingScript.addEventListener('load', () => { - this.state.zxingReady = typeof ZXing !== 'undefined'; - resolve(this.state.zxingReady); - }, { once: true }); - existingScript.addEventListener('error', () => { - console.error('Failed to load ZXing library'); - resolve(false); - }, { once: true }); - return; - } - - const script = document.createElement('script'); - script.src = scriptUrl; - script.async = true; - script.onload = () => { - this.state.zxingReady = typeof ZXing !== 'undefined'; - resolve(this.state.zxingReady); - }; - script.onerror = () => { - console.error('Failed to load ZXing library'); - resolve(false); - }; - document.head.appendChild(script); - }); - } - - /** - * Start real-time barcode scanning via camera - */ - async start() { - if (this.state.isRunning) { - return; - } - - try { - // Ensure ZXing is loaded - const zxingReady = await this.initZXing(); - if (!zxingReady) { - this.options.onError('ZXing library failed to load. Please use image upload fallback.'); - return; - } - - // Get DOM elements - this.state.videoElement = document.getElementById(this.options.videoId); - this.state.canvas = document.getElementById(this.options.canvasId); - - if (!this.state.videoElement || !this.state.canvas) { - this.options.onError('Scanner video or canvas element not found.'); - return; - } - - // Request camera access - const stream = await navigator.mediaDevices.getUserMedia({ - video: { - facingMode: this.options.facingMode, - width: { ideal: 1280 }, - height: { ideal: 720 }, - }, - audio: false, - }); - - this.state.stream = stream; - this.state.videoElement.srcObject = stream; - - // Make video element visible (ensure it's not hidden by display: none) - this.state.videoElement.style.display = 'block'; - // Ensure autoplay works on mobile by muting and setting playsInline - try { - this.state.videoElement.muted = true; - this.state.videoElement.autoplay = true; - this.state.videoElement.playsInline = true; - // Some browsers require explicit play() after setting srcObject - const p = this.state.videoElement.play(); - if (p && p.then) { - p.catch(() => { - // ignore play promise rejection (user gesture required on some browsers) - }); - } - } catch (e) { - // ignore - } - - // Initialize ZXing reader - const hints = new Map(); - hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, this.options.formats); - hints.set(ZXing.DecodeHintType.TRY_HARDER, true); - this.state.reader = new ZXing.BrowserMultiFormatReader(hints); - - this.state.isRunning = true; - - // Start scanning loop - this.scanLoop(); - - // Setup keyboard input fallback for USB scanners - this.setupKeyboardInput(); - } catch (err) { - this.options.onError(`Camera error: ${err.message || err}`); - } - } - - /** - * Stop barcode scanning - */ - stop() { - if (!this.state.isRunning) { - return; - } - - this.state.isRunning = false; - - if (this.state.stream) { - this.state.stream.getTracks().forEach((track) => track.stop()); - this.state.stream = null; - } - - if (this.state.videoElement) { - this.state.videoElement.srcObject = null; - this.state.videoElement.style.display = 'none'; - } - - // Reset ZXing reader to stop any internal loops - try { - if (this.state.reader && typeof this.state.reader.reset === 'function') { - this.state.reader.reset(); - } - } catch (e) { - // ignore - } - this.state.reader = null; - - this.removeKeyboardInput(); - } - - /** - * Main scanning loop for real-time frame processing - */ - scanLoop() { - if (!this.state.isRunning) { - return; - } - - const canvas = this.state.canvas; - const video = this.state.videoElement; - - if (!video || !canvas) { - setTimeout(() => this.scanLoop(), 100); - return; - } - - if (video.readyState === video.HAVE_ENOUGH_DATA) { - try { - canvas.width = video.videoWidth; - canvas.height = video.videoHeight; - - if (canvas.width === 0 || canvas.height === 0) { - setTimeout(() => this.scanLoop(), 100); - return; - } - - const context = canvas.getContext('2d'); - - if (context) { - context.drawImage(video, 0, 0); - - try { - const luminanceSource = new ZXing.HTMLCanvasElementLuminanceSource(canvas); - const binaryBitmap = new ZXing.BinaryBitmap(new ZXing.HybridBinarizer(luminanceSource)); - - try { - const result = this.state.reader.decodeFromBitmap(binaryBitmap); - this.handleScanResult(result.text); - } catch (e) { - // No barcode found in frame; continue scanning - } - } catch (err) { - // Silently ignore canvas errors - } - } - } catch (err) { - console.warn('Scan loop error:', err); - } - } - - // Throttle scan loop based on FPS setting - setTimeout(() => this.scanLoop(), 1000 / this.options.fps); - } - - /** - * Handle successful barcode scan with debouncing - */ - handleScanResult(scannedText) { - const now = Date.now(); - if (now - this.state.lastScanTime < this.state.scanDebounceMs) { - return; // Ignore rapid duplicates - } - - this.state.lastScanTime = now; - this.options.onSuccess(scannedText.trim()); - } - - /** - * Setup keyboard input listener for USB scanner emulation - * Most USB scanners emulate keyboard input (barcode followed by Enter) - */ - setupKeyboardInput() { - this.keyboardListener = (event) => { - // Allow only alphanumeric, dash, colon (common in barcodes) - if (/^[a-zA-Z0-9:_\-]$/.test(event.key)) { - event.preventDefault(); - this.keyboardInputBuffer += event.key; - - // Reset timeout for multi-part barcodes - if (this.keyboardInputTimeout) { - clearTimeout(this.keyboardInputTimeout); - } - } else if (event.key === 'Enter') { - event.preventDefault(); - if (this.keyboardInputBuffer.length > 0) { - this.handleScanResult(this.keyboardInputBuffer); - this.keyboardInputBuffer = ''; - } - } else if (event.key === 'Escape') { - this.keyboardInputBuffer = ''; - this.stop(); - } - - // Auto-trigger if buffer looks complete (common barcode lengths) - if ([8, 12, 13, 17].includes(this.keyboardInputBuffer.length)) { - this.keyboardInputTimeout = setTimeout(() => { - if (this.keyboardInputBuffer.length > 0) { - this.handleScanResult(this.keyboardInputBuffer); - this.keyboardInputBuffer = ''; - } - }, 200); - } - }; - - document.addEventListener('keydown', this.keyboardListener); - } - - /** - * Remove keyboard input listener - */ - removeKeyboardInput() { - if (this.keyboardListener) { - document.removeEventListener('keydown', this.keyboardListener); - this.keyboardListener = null; - } - this.keyboardInputBuffer = ''; - } - - /** - * Upload image for server-side decoding (fallback) - * Requires /api/scan_upload endpoint - */ - static async uploadImageForDecoding(file, onSuccess, onError) { - try { - const formData = new FormData(); - formData.append('image', file); - - const response = await fetch('/api/scan_upload', { - method: 'POST', - body: formData, - }); - - const data = await response.json(); - - if (data.ok && data.barcodes && data.barcodes.length > 0) { - // Return first detected barcode (highest confidence) - onSuccess(data.barcodes[0].value); - } else { - onError(data.message || 'No barcode detected in image.'); - } - } catch (err) { - onError(`Upload error: ${err.message}`); - } - } - - /** - * Toggle camera (front/rear on mobile) - */ - toggleCamera() { - if (this.options.facingMode === 'environment') { - this.options.facingMode = 'user'; - } else { - this.options.facingMode = 'environment'; - } - - // Restart scanning with new camera - this.stop(); - this.start(); - } -} - -// Export for use in templates -if (typeof window !== 'undefined') { - window.HybridScanner = HybridScanner; -} diff --git a/Web/templates/library.html b/Web/templates/library.html index 2bdf5d5..94e7bc7 100644 --- a/Web/templates/library.html +++ b/Web/templates/library.html @@ -67,22 +67,8 @@ @@ -220,8 +206,7 @@ - - + - - +