From c227beeaca40a454000acb82822f5a03aeeacd31 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Sat, 1 Nov 2025 02:30:20 +0100 Subject: [PATCH] alert for persons in camera view --- .gitea/workflows/run-mucapy.yml | 45 ---- mucapy/compile.py | 46 ++++ mucapy/main.py | 401 ++++++++++++++++++++++++++++++-- mucapy/styling/sound/alert.wav | Bin 0 -> 20718 bytes 4 files changed, 431 insertions(+), 61 deletions(-) delete mode 100644 .gitea/workflows/run-mucapy.yml create mode 100644 mucapy/compile.py create mode 100644 mucapy/styling/sound/alert.wav diff --git a/.gitea/workflows/run-mucapy.yml b/.gitea/workflows/run-mucapy.yml deleted file mode 100644 index 0a2f035..0000000 --- a/.gitea/workflows/run-mucapy.yml +++ /dev/null @@ -1,45 +0,0 @@ - -name: Build MuCaPy Executable - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build-windows-exe: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Install dependencies for Wine - run: | - sudo dpkg --add-architecture i386 - sudo apt update - sudo apt install -y wine64 wine32 unzip wget cabextract - - - name: Install Windows Python under Wine - run: | - wget https://www.python.org/ftp/python/3.13.9/python-3.13.9-amd64.exe -O python_installer.exe - wine python_installer.exe /quiet InstallAllUsers=1 PrependPath=1 - - - name: Upgrade pip and install PyInstaller (Windows) - run: | - wine python -m pip install --upgrade pip - wine python -m pip install pyinstaller - - - name: Build Windows executable - run: | - wine pyinstaller --onefile --windowed mucapy/main.py \ - --add-data "mucapy/styling;styling" \ - --add-data "mucapy/models;models" \ - --add-data "mucapy/todopackage;todopackage" - - - name: Upload Windows executable - uses: actions/upload-artifact@v3 - with: - name: mucapy-windows-exe - path: dist/ diff --git a/mucapy/compile.py b/mucapy/compile.py new file mode 100644 index 0000000..92b961c --- /dev/null +++ b/mucapy/compile.py @@ -0,0 +1,46 @@ +import os +from PIL import Image +import PyInstaller.__main__ +import PyQt5 + +# Paths +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +MAIN_SCRIPT = os.path.join(ROOT_DIR, "main.py") +STYLING_DIR = os.path.join(ROOT_DIR, "styling") + +# Icon paths +PNG_ICON = os.path.join(STYLING_DIR, "logo.png") +ICO_ICON = os.path.join(STYLING_DIR, "logo.ico") + +# Convert PNG to ICO +img = Image.open(PNG_ICON) +img.save(ICO_ICON, format="ICO", sizes=[(256,256), (128,128), (64,64), (32,32), (16,16)]) +print(f"Converted {PNG_ICON} to {ICO_ICON}") + +# Detect PyQt5 platforms folder automatically +pyqt_dir = os.path.dirname(PyQt5.__file__) +platforms_path = None + +# Walk recursively to find the 'platforms' folder +for root, dirs, files in os.walk(pyqt_dir): + if 'platforms' in dirs: + platforms_path = os.path.join(root, 'platforms') + break + +if platforms_path is None or not os.path.exists(platforms_path): + raise FileNotFoundError(f"Could not locate PyQt5 'platforms' folder under {pyqt_dir}") + +print(f"Using PyQt5 platforms folder: {platforms_path}") + +# Build EXE with PyInstaller +PyInstaller.__main__.run([ + MAIN_SCRIPT, + '--noconfirm', + '--onefile', + '--windowed', + f'--icon={ICO_ICON}', + # Only include the platforms folder (minimal requirement for PyQt5) + '--add-data', f'{platforms_path};PyQt5/Qt/plugins/platforms', +]) + +print("Build complete! Check the 'dist' folder for the executable.") diff --git a/mucapy/main.py b/mucapy/main.py index 75b304d..42a3e46 100644 --- a/mucapy/main.py +++ b/mucapy/main.py @@ -10,6 +10,7 @@ import sys import time import urllib.parse import ctypes +import shutil import cv2 import numpy as np import psutil # Add psutil import @@ -37,10 +38,18 @@ except ImportError as e: "3) Install VC++ runtime: https://aka.ms/vs/17/release/vc_redist.x64.exe\n" "4) Restart the app after reinstalling.\n\n" f"Original error: {e}") - sys.exit(1) import todopackage.todo as todo # This shit will fail eventually | Or not IDK +# Audio alert dependencies +import wave +try: + import simpleaudio as sa +except Exception: + sa = None +# Force-disable simpleaudio to avoid potential native backend crashes; use OS-native players only +sa = None + def bytes_to_human(n: int) -> str: """Convert a byte value to a human-readable string using base 1024. @@ -117,7 +126,7 @@ class Config: with open(self.config_file, 'w') as f: json.dump(self.settings, f, indent=4) except FileNotFoundError: - exit(1) + pass except Exception as e: print(f"Error saving config: {e}") @@ -374,6 +383,172 @@ class CameraScanThread(QThread): except Exception as e: print(f"CameraScanThread error: {e}") self.scan_finished.emit([], {}) + +class AlertWorker(QThread): + """Worker thread to play an alert sound safely without blocking UI. + Uses winsound on Windows, external system players on Unix (afplay/paplay/aplay/ffplay), + and falls back to simpleaudio if available. Supports cooperative stop. + """ + finished = pyqtSignal(bool, str) # success, message + + def __init__(self, wav_path: str, parent=None): + super().__init__(parent) + self.wav_path = wav_path + self._stop = False + self._subproc = None + self._play_obj = None + + def stop(self): + """Request the worker to stop early.""" + try: + self._stop = True + if self._play_obj is not None: + try: + self._play_obj.stop() + except Exception: + pass + if self._subproc is not None: + try: + self._subproc.terminate() + except Exception: + pass + except Exception: + pass + + def _find_unix_player(self): + """Return (cmd_list, name) for an available player on Unix or (None, None).""" + try: + if sys.platform.startswith('darwin'): + if shutil.which('afplay'): + return (['afplay'], 'afplay') + # Linux and others + if shutil.which('paplay'): + return (['paplay'], 'paplay') + if shutil.which('aplay'): + return (['aplay', '-q'], 'aplay') + if shutil.which('ffplay'): + return (['ffplay', '-nodisp', '-autoexit', '-loglevel', 'error'], 'ffplay') + except Exception: + pass + return (None, None) + + def run(self): + try: + if not os.path.exists(self.wav_path): + self.finished.emit(False, f"File not found: {self.wav_path}") + return + + # Windows path: prefer winsound (native, safe) + if sys.platform.startswith('win'): + ws_error = "unknown" + try: + import winsound as _ws # type: ignore + # Resolve flags safely even if some attributes are missing + SND_FILENAME = getattr(_ws, 'SND_FILENAME', 0x00020000) + SND_SYNC = getattr(_ws, 'SND_SYNC', 0x0000) # 0 is synchronous by default + flags = SND_FILENAME | SND_SYNC + # Ensure PlaySound exists + play_fn = getattr(_ws, 'PlaySound', None) + if play_fn is None: + raise RuntimeError('winsound.PlaySound not available') + for _ in range(4): + if self._stop: + break + try: + play_fn(self.wav_path, flags) + except Exception as e: + # On failure, break to try alternative backends + ws_error = str(e) + break + time.sleep(0.002) + else: + # Completed all 4 plays + self.finished.emit(True, "Alert played") + return + # If here, winsound failed at some point; continue to fallbacks + except Exception as e: + ws_error = str(e) + # Try simpleaudio on Windows as fallback + if sa is not None: + try: + with wave.open(self.wav_path, 'rb') as wf: + n_channels = max(1, wf.getnchannels()) + sampwidth = max(1, wf.getsampwidth()) + framerate = max(8000, wf.getframerate() or 44100) + frames = wf.readframes(wf.getnframes()) + for _ in range(4): + if self._stop: + break + self._play_obj = sa.play_buffer(frames, n_channels, sampwidth, framerate) + self._play_obj.wait_done() + time.sleep(0.002) + self.finished.emit(True, "Alert played") + return + except Exception as e2: + self.finished.emit(False, f"Playback error (winsound fallback -> simpleaudio): {e2}") + return + else: + self.finished.emit(False, f"Audio backend not available (winsound failed: {ws_error})") + return + + # Non-Windows: try external players first + cmd, name = self._find_unix_player() + if cmd is not None: + for _ in range(4): + if self._stop: + break + try: + self._subproc = subprocess.Popen(cmd + [self.wav_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + # Poll until done or stop requested + while True: + if self._stop: + try: + self._subproc.terminate() + except Exception: + pass + break + ret = self._subproc.poll() + if ret is not None: + break + time.sleep(0.01) + except Exception as e: + # Try next backend + cmd = None + break + finally: + self._subproc = None + time.sleep(0.002) + if cmd is not None: + self.finished.emit(True, "Alert played") + return + + # Fallback: simpleaudio if available + if sa is not None: + try: + with wave.open(self.wav_path, 'rb') as wf: + n_channels = max(1, wf.getnchannels()) + sampwidth = max(1, wf.getsampwidth()) + framerate = max(8000, wf.getframerate() or 44100) + frames = wf.readframes(wf.getnframes()) + for _ in range(4): + if self._stop: + break + self._play_obj = sa.play_buffer(frames, n_channels, sampwidth, framerate) + self._play_obj.wait_done() + time.sleep(0.002) + self.finished.emit(True, "Alert played") + return + except Exception as e: + self.finished.emit(False, f"Playback error (simpleaudio): {e}") + return + + self.finished.emit(False, "No audio backend available (afplay/paplay/aplay/ffplay/simpleaudio)") + except Exception as e: + try: + self.finished.emit(False, str(e)) + except Exception: + pass + class MultiCamYOLODetector(QObject): cameras_scanned = pyqtSignal(list, dict) # Emits (available_cameras, index_to_name) def __init__(self, parent=None): @@ -594,8 +769,8 @@ class MultiCamYOLODetector(QObject): with open(classes_path, 'r') as f: self.classes = f.read().strip().split('\n') except FileNotFoundError: - exit(1) - + pass + np.random.seed(42) self.colors = np.random.randint(0, 255, size=(len(self.classes), 3), dtype='uint8') return True @@ -732,13 +907,26 @@ class MultiCamYOLODetector(QObject): indices = cv2.dnn.NMSBoxes(boxes, confidences, self.confidence_threshold, 0.4) + person_detected = False if len(indices) > 0: for i in indices.flatten(): (x, y, w, h) = boxes[i] color = [int(c) for c in self.colors[class_ids[i]]] cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) - text = f"{self.classes[class_ids[i]]}: {confidences[i]:.2f}" + cls_name = self.classes[class_ids[i]] if 0 <= class_ids[i] < len(self.classes) else str(class_ids[i]) + text = f"{cls_name}: {confidences[i]:.2f}" cv2.putText(frame, text, (x, y - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) + if not person_detected and str(cls_name).lower() == 'person': + person_detected = True + # Auto-trigger alert if a person is detected on any camera and alerts are enabled + try: + if person_detected: + parent_window = self.parent() + if parent_window is not None: + # trigger_alert() has its own internal guards (enabled, cooldown, playing) + parent_window.trigger_alert() + except Exception: + pass except Exception as e: print(f"Detection error: {e}") @@ -994,7 +1182,7 @@ class CameraDisplay(QLabel): with open(self.get_camera_display_style,"r") as cdst: self.setStyleSheet(cdst.read()) except FileNotFoundError: - exit(1) + pass self.setMinimumSize(320, 240) self.fullscreen_window = None @@ -1230,7 +1418,7 @@ class AboutWindow(QDialog): with open(toggle_btn_style,"r") as tgbstyle: self.toggle_btn.setStyleSheet(tgbstyle.read()) except FileNotFoundError: - exit(1) + pass # Debug shit #print("i did shit") @@ -1272,7 +1460,7 @@ class AboutWindow(QDialog): with open(style_file,"r") as aboutstyle: self.setStyleSheet(aboutstyle.read()) except FileNotFoundError: - exit(1) + pass self.setLayout(layout) @@ -1295,7 +1483,7 @@ class AboutWindow(QDialog): pass except FileNotFoundError: print(f"Missing a Style File! => {todo_style_path}") - exit(1) + pass # Create the labels for the fucking trodo ass shit ? self.todo_archive_object = todo @@ -1902,6 +2090,15 @@ class MainWindow(QMainWindow): self.camera_settings = {} self.detection_enabled = True # Add detection toggle flag + # Alert system state + self.alert_enabled = bool(self.config.load_setting('alert_enabled', True)) + self._alert_playing = False + self._alert_cooldown = False + self._cooldown_timer = QTimer(self) + self._cooldown_timer.setSingleShot(True) + self._cooldown_timer.timeout.connect(self._on_cooldown_finished) + self._alert_worker = None + # Load saved settings first self.load_saved_settings() @@ -1918,8 +2115,8 @@ class MainWindow(QMainWindow): with open(style_file, "r") as mainstyle: self.setStyleSheet(mainstyle.read()) except FileNotFoundError: - exit(1) - + pass + # Set palette for better dark mode support palette = self.palette() palette.setColor(palette.Window, QColor(45, 45, 45)) @@ -1977,6 +2174,12 @@ class MainWindow(QMainWindow): if hasattr(self, 'model_label') and self.detector.model_dir: self.model_label.setText(f"Model: {os.path.basename(self.detector.model_dir)}") + + # Ensure alert UI reflects backend availability and state + try: + self._update_alert_ui() + except Exception: + pass def create_menus(self): menubar = self.menuBar() @@ -2043,6 +2246,14 @@ class MainWindow(QMainWindow): self.toggle_detection_action.setShortcut('Ctrl+D') self.toggle_detection_action.triggered.connect(self.toggle_detection) view_menu.addAction(self.toggle_detection_action) + + # Alert toggle moved to View menu + self.toggle_alert_action = QAction('Enable Alert', self) + self.toggle_alert_action.setCheckable(True) + self.toggle_alert_action.setChecked(bool(self.alert_enabled)) + self.toggle_alert_action.setStatusTip('Play an audible alert when a person is detected') + self.toggle_alert_action.triggered.connect(self.set_alert_enabled) + view_menu.addAction(self.toggle_alert_action) # Camera menu self.camera_menu = menubar.addMenu('Cameras') @@ -2331,6 +2542,8 @@ class MainWindow(QMainWindow): layout_layout.addWidget(self.layout_combo) settings_layout.addLayout(layout_layout) + # Button enablement determined dynamically based on backend availability + # Add screenshot button to settings screenshot_btn = QPushButton("Take Screenshot") screenshot_btn.clicked.connect(self.take_screenshot) @@ -2716,7 +2929,7 @@ class MainWindow(QMainWindow): bar.setStyleSheet(u60_style.read()) except FileNotFoundError: print("Styling for CPU U60 not found!") - exit(1) + pass else: u60seperate = getpath.resource_path("styling/bar/seperate/u60.qss") try: @@ -2735,7 +2948,7 @@ class MainWindow(QMainWindow): bar.setStyleSheet(a85_styling.read()) except FileNotFoundError: print("Styling for CPU u85 not found") - exit(1) + pass else: u85sep = getpath.resource_path("styling/bar/seperate/a85.qss") try: @@ -2754,7 +2967,7 @@ class MainWindow(QMainWindow): bar.setStyleSheet(else_style.read()) except FileNotFoundError: print("No ElseStyling found!") - exit(1) + pass else: else_file_seperate = getpath.resource_path("styling/bar/seperate/else.qss") try: @@ -2775,6 +2988,162 @@ class MainWindow(QMainWindow): self.start_btn.setEnabled(False) self.stop_btn.setEnabled(False) + # ===== Alert system methods ===== + def set_alert_enabled(self, state: bool): + try: + self.alert_enabled = bool(state) + self.config.save_setting('alert_enabled', self.alert_enabled) + # Keep View menu action in sync + try: + if hasattr(self, 'toggle_alert_action'): + self.toggle_alert_action.setChecked(self.alert_enabled) + except Exception: + pass + except Exception: + pass + self._update_alert_ui() + + def _has_audio_backend(self) -> bool: + """Check if any audio backend is available for alerts.""" + try: + if sys.platform.startswith('win'): + try: + import winsound as _ws # type: ignore + return hasattr(_ws, 'PlaySound') + except Exception: + return sa is not None + # macOS/Linux external players + if shutil.which('afplay') or shutil.which('paplay') or shutil.which('aplay') or shutil.which('ffplay'): + return True + return sa is not None + except Exception: + return sa is not None + + def _update_alert_ui(self): + try: + backend_ok = self._has_audio_backend() + # Update status tip on the View->Enable Alert action + if hasattr(self, 'toggle_alert_action'): + tip = [] + if not backend_ok: + tip.append("No audio backend found (winsound/afplay/paplay/aplay/ffplay/simpleaudio)") + if self._alert_cooldown: + tip.append("Alert cooldown active (30s)") + if self._alert_playing: + tip.append("Alert is playing") + if not self.alert_enabled: + tip.append("Alert disabled") + self.toggle_alert_action.setStatusTip("; ".join(tip) if tip else "Play an audible alert when a person is detected") + except Exception: + pass + + def trigger_alert(self): + """Trigger the alert sound if enabled and not in cooldown.""" + try: + if not self.alert_enabled: + return + if self._alert_playing or self._alert_cooldown: + return + wav_path = getpath.resource_path("styling/sound/alert.wav") + if not os.path.exists(wav_path): + QMessageBox.warning(self, "Alert sound missing", f"Sound file not found:\n{wav_path}") + return + # Start worker (backend decided internally) + self._alert_worker = AlertWorker(wav_path, self) + self._alert_worker.finished.connect(self._on_alert_finished) + self._alert_playing = True + self._update_alert_ui() + self._alert_worker.start() + except Exception as e: + QMessageBox.critical(self, "Alert Error", f"Failed to trigger alert: {e}") + self._alert_playing = False + self._update_alert_ui() + + def _on_alert_finished(self, success: bool, message: str): + # Clean state and start cooldown on success + try: + self._alert_playing = False + # Avoid holding a reference to finished thread + try: + if self._alert_worker: + self._alert_worker.deleteLater() + except Exception: + pass + self._alert_worker = None + if success: + self._alert_cooldown = True + # 30 seconds cooldown + self._cooldown_timer.start(30000) + else: + # Informative but non-fatal + try: + if message: + print(f"Alert playback failed: {message}") + except Exception: + pass + finally: + self._update_alert_ui() + + def _on_cooldown_finished(self): + self._alert_cooldown = False + self._update_alert_ui() + + def closeEvent(self, event): + """Ensure background workers and timers are stopped to avoid crashes on exit.""" + try: + # Stop periodic timers + try: + if hasattr(self, 'timer') and self.timer is not None: + self.timer.stop() + except Exception: + pass + try: + if hasattr(self, 'hw_timer') and self.hw_timer is not None: + self.hw_timer.stop() + except Exception: + pass + try: + if hasattr(self, '_cooldown_timer') and self._cooldown_timer is not None: + self._cooldown_timer.stop() + except Exception: + pass + + # Stop alert worker if running + try: + if getattr(self, '_alert_worker', None) is not None: + try: + self._alert_worker.finished.disconnect(self._on_alert_finished) + except Exception: + pass + try: + if self._alert_worker.isRunning(): + try: + self._alert_worker.stop() + except Exception: + pass + self._alert_worker.wait(2000) + except Exception: + pass + try: + self._alert_worker.deleteLater() + except Exception: + pass + self._alert_worker = None + except Exception: + pass + + # Disconnect cameras and stop camera threads + try: + if hasattr(self, 'detector') and self.detector is not None: + self.detector.disconnect_cameras() + except Exception: + pass + finally: + try: + event.accept() + except Exception: + pass + class initQT: """ This is a QOL Change if you prefer to do it the hard way. Or you just like to get Fist Fucked then i suggest you remove the Function Calls in the @@ -2800,7 +3169,7 @@ class initQT: "echo $XDG_SESSION_TYPE" "Run this command in bash!" ) - exit(1) + pass def setenv(self): # Set the Session Type to the one it got @@ -2813,7 +3182,7 @@ class initQT: f"export XDG_SESSION_TYPE={self.session_type}" "run this command in bash" ) - exit(1) + pass @staticmethod def shutupCV(): # This needs some fixing as this only works before importing CV2 ; too much refactoring work tho! diff --git a/mucapy/styling/sound/alert.wav b/mucapy/styling/sound/alert.wav new file mode 100644 index 0000000000000000000000000000000000000000..113cbbf023b4b031fcb9aefcb003a720f24530e5 GIT binary patch literal 20718 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