127 lines
3.7 KiB
Markdown
127 lines
3.7 KiB
Markdown
# 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"})`
|