Files
duck-preview/notes/02-mvp-mac.md

3.7 KiB

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:

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

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

pip install nuitka
pyside6-deploy duck_preview/__main__.py --name "Duck Preview"

pyside6-deploy.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"})