# macOS — Camera on macOS with PySide6 ## Problem Aplikacja uruchomiona w interpreted mode (`python -m duck_preview`) na macOS z kamerą Elgato nie wyświetla obrazu. ## Przyczyna Oficjalny przykład PySide6 (`camera.qml` / examples/multimedia) zawiera kod: ```python if sys.platform == "darwin": is_nuitka = "__compiled__" in globals() if not is_nuitka and sys.platform == "darwin": print("This example does not work on macOS when Python is run " "in interpreted mode. For this example to work on macOS, " "package the example using pyside6-deploy") sys.exit(0) ``` **macOS → AVFoundation → wymaga spakowania przez Nuitka/pyside6-deploy.** `QCamera` na macOS potrzebuje properly bundled app structure (Info.plist, entitlements, code signing). W interpreted mode Python nie ma tego contextu. ## Rozwiązanie Pakować aplikację przez `pyside6-deploy` (Nuitka) na macOS. Na Windows działa bez pakowania. --- ## Qt Permission API (`QCameraPermission`) Od Qt 6.5+ dostępne jest `QCameraPermission` — nowoczesne API do proszenia o zgodę kamery. ### API ```python from PySide6.QtCore import QCameraPermission, Qt from PySide6.QtWidgets import QApplication perm = QCameraPermission() match QApplication.checkPermission(perm): case Qt.PermissionStatus.Undetermined: # Prosimy o zgodę → callback wywoła init ponownie QApplication.requestPermission(perm, parent, callback) return case Qt.PermissionStatus.Denied: # overlay: "Camera permission denied" case Qt.PermissionStatus.Granted: # uruchom kamerę ``` ### Permission flow ``` User → wybiera kamerę w menu → MainWindow._on_camera_selected(device) → checkPermission(QCameraPermission) ├─ Undetermined → requestPermission(camera, parent, callback) │ └─ callback → _on_camera_selected ponownie ├─ Denied → overlay: "Camera permission denied. Grant access in System Settings > Privacy & Security > Camera" └─ Granted → CameraService.start(device) ``` --- ## QVideoWidget + QVideoSink — dual output PySide6 6.11 ma osobne metody w `QMediaCaptureSession`: | Metoda | Przeznaczenie | |--------|---------------| | `setVideoOutput(QVideoWidget*)` | Natywne renderowanie (GPU) | | `setVideoSink(QVideoSink*)` | Dostęp do klatek (telemetria, AI) | Oba działają **równolegle** — nie ma konfliktu. ``` QMediaCaptureSession ├─ setVideoOutput(QVideoWidget) → natywne renderowanie └─ setVideoSink(QVideoSink) → frame access └─ videoFrameChanged → FrameDispatcher ``` QVideoSink dostarcza sygnał `videoFrameChanged(QVideoFrame)` — na to podpina się FrameDispatcher, a ten rozsyła do TelemetryCollector i przyszłych AI subscriberów. --- ## macOS — packaging ### pyside6-deploy ```bash pip install nuitka pyside6-deploy duck_preview/__main__.py --name "Duck Preview" ``` ### pyside6-deploy.toml ```toml [app] script = "duck_preview/__main__.py" name = "Duck Preview" bundle_identifier = "com.bartool.duck-preview" categories = "public.app-category.photography" platforms = ["macos"] ``` Po spakowaniu powstaje `Duck Preview.app` — standalone bundle z dostępem do AVFoundation. ### Windows Na Windows działa bez pakowania — `python -m duck_preview` w venv. --- ## Font fallback `Consolas` nie istnieje na macOS. Używać `"monospace"` (generic font family — Qt mapuje na Menlo na macOS, Consolas na Windows). ## Error state w Overlay Overlay wspiera wyświetlanie błędów (np. brak permisji): - Normalny stan → zielone metryki (FPS, frame times) - Error state → czerwony komunikat - Przełączanie przez `overlay.set_metrics({"error": "message"})`