119 lines
4.5 KiB
Python
119 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
from PySide6.QtGui import QAction, QCloseEvent
|
|
from PySide6.QtMultimedia import QCameraDevice, QMediaDevices
|
|
from PySide6.QtWidgets import QGridLayout, QMainWindow, QWidget
|
|
|
|
from duck_preview.camera.service import CameraService
|
|
from duck_preview.rendering.overlay import OverlayWidget
|
|
from duck_preview.rendering.video_widget import VideoWidget
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(
|
|
self,
|
|
camera_service: CameraService,
|
|
video_widget: VideoWidget,
|
|
overlay_widget: OverlayWidget,
|
|
parent: QWidget | None = None,
|
|
) -> None:
|
|
super().__init__(parent)
|
|
self._camera = camera_service
|
|
self._video_widget = video_widget
|
|
self._overlay = overlay_widget
|
|
self._media_devices = QMediaDevices()
|
|
|
|
self.setWindowTitle("Duck Preview")
|
|
self.resize(1280, 720)
|
|
|
|
central = QWidget()
|
|
self.setCentralWidget(central)
|
|
layout = QGridLayout(central)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
layout.addWidget(video_widget, 0, 0)
|
|
layout.addWidget(overlay_widget, 0, 0)
|
|
|
|
self._setup_menus()
|
|
|
|
self._media_devices.videoInputsChanged.connect(self._rebuild_camera_menu)
|
|
|
|
def _setup_menus(self) -> None:
|
|
menu_bar = self.menuBar()
|
|
|
|
self._camera_menu = menu_bar.addMenu("Camera")
|
|
self._rebuild_camera_menu()
|
|
|
|
self._resolution_menu = menu_bar.addMenu("Resolution")
|
|
self._resolution_menu.setEnabled(False)
|
|
|
|
self._fps_menu = menu_bar.addMenu("FPS")
|
|
self._fps_menu.setEnabled(False)
|
|
|
|
debug_menu = menu_bar.addMenu("Debug")
|
|
toggle_overlay = QAction("Show Metrics", self)
|
|
toggle_overlay.setCheckable(True)
|
|
toggle_overlay.setChecked(True)
|
|
toggle_overlay.triggered.connect(self._overlay.set_visible)
|
|
debug_menu.addAction(toggle_overlay)
|
|
|
|
def _rebuild_camera_menu(self) -> None:
|
|
self._camera_menu.clear()
|
|
cameras = CameraService.available_cameras()
|
|
for device in cameras:
|
|
action = QAction(device.description(), self)
|
|
action.triggered.connect(lambda checked, d=device: self._on_camera_selected(d))
|
|
self._camera_menu.addAction(action)
|
|
if not cameras:
|
|
action = QAction("No cameras detected", self)
|
|
action.setEnabled(False)
|
|
self._camera_menu.addAction(action)
|
|
|
|
def _on_camera_selected(self, device: QCameraDevice) -> None:
|
|
self._camera.start(device)
|
|
self._rebuild_resolution_menu(device)
|
|
|
|
def _rebuild_resolution_menu(self, device: QCameraDevice) -> None:
|
|
self._resolution_menu.clear()
|
|
self._resolution_menu.setEnabled(True)
|
|
formats = device.videoFormats()
|
|
seen: set[tuple[int, int]] = set()
|
|
for fmt in formats:
|
|
res = fmt.resolution()
|
|
key = (res.width(), res.height())
|
|
if key not in seen:
|
|
seen.add(key)
|
|
action = QAction(f"{res.width()}x{res.height()}", self)
|
|
action.triggered.connect(
|
|
lambda checked, d=device, w=res.width(), h=res.height(): (
|
|
self._on_resolution_selected(d, w, h)
|
|
)
|
|
)
|
|
self._resolution_menu.addAction(action)
|
|
|
|
def _on_resolution_selected(self, device: QCameraDevice, width: int, height: int) -> None:
|
|
self._rebuild_fps_menu(device, width, height)
|
|
|
|
def _rebuild_fps_menu(self, device: QCameraDevice, width: int, height: int) -> None:
|
|
self._fps_menu.clear()
|
|
self._fps_menu.setEnabled(True)
|
|
formats = device.videoFormats()
|
|
seen_labels: set[str] = set()
|
|
for fmt in formats:
|
|
res = fmt.resolution()
|
|
if res.width() == width and res.height() == height:
|
|
min_fps = round(fmt.minFrameRate())
|
|
max_fps = round(fmt.maxFrameRate())
|
|
label = f"{max_fps} FPS" if min_fps == max_fps else f"{min_fps}-{max_fps} FPS"
|
|
if label not in seen_labels:
|
|
seen_labels.add(label)
|
|
action = QAction(label, self)
|
|
action.triggered.connect(
|
|
lambda checked, f=fmt: self._camera.set_camera_format(f)
|
|
)
|
|
self._fps_menu.addAction(action)
|
|
|
|
def closeEvent(self, event: QCloseEvent) -> None: # noqa: N802
|
|
self._camera.stop()
|
|
super().closeEvent(event)
|