363 lines
10 KiB
Python
363 lines
10 KiB
Python
import sys
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from PySide6.QtCore import Qt, QTimer
|
|
from PySide6.QtGui import QAction
|
|
from PySide6.QtWidgets import QApplication, QMainWindow, QLabel
|
|
from PySide6.QtMultimedia import (
|
|
QMediaDevices,
|
|
QCamera,
|
|
QCameraDevice,
|
|
QMediaFormat,
|
|
QVideoFrameFormat,
|
|
QMediaCaptureSession
|
|
)
|
|
from PySide6.QtMultimediaWidgets import QVideoWidget
|
|
|
|
|
|
# ============================================================
|
|
# LOGGER
|
|
# ============================================================
|
|
|
|
LOG_DIR = Path("logs")
|
|
LOG_DIR.mkdir(exist_ok=True)
|
|
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
handlers=[
|
|
logging.FileHandler(LOG_DIR / "camera_debug.log", encoding="utf-8"),
|
|
logging.StreamHandler(sys.stdout),
|
|
],
|
|
)
|
|
|
|
logger = logging.getLogger("camera_app")
|
|
|
|
|
|
# ============================================================
|
|
# CAMERA WINDOW
|
|
# ============================================================
|
|
|
|
class CameraWindow(QMainWindow):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.setWindowTitle("Camera Preview")
|
|
|
|
# Widget wyświetlający wyłącznie obraz
|
|
self.video_widget = QVideoWidget()
|
|
self.setCentralWidget(self.video_widget)
|
|
|
|
self.camera = None
|
|
self.camera_device = None
|
|
self.capture_session = QMediaCaptureSession()
|
|
|
|
self.init_camera()
|
|
|
|
# ========================================================
|
|
# CAMERA INIT
|
|
# ========================================================
|
|
|
|
def init_camera(self):
|
|
|
|
devices = QMediaDevices.videoInputs()
|
|
|
|
logger.info("==========================================")
|
|
logger.info("VIDEO DEVICES ENUMERATION")
|
|
logger.info("==========================================")
|
|
|
|
if not devices:
|
|
logger.error("Nie znaleziono kamer.")
|
|
return
|
|
|
|
for idx, device in enumerate(devices):
|
|
self.log_camera_device(device, idx)
|
|
|
|
# wybór pierwszej kamery
|
|
self.camera_device = devices[0]
|
|
|
|
logger.info(f"Wybrano kamerę: {self.camera_device.description()}")
|
|
|
|
self.camera = QCamera(self.camera_device)
|
|
|
|
# sygnały
|
|
self.camera.errorOccurred.connect(self.on_camera_error)
|
|
self.camera.activeChanged.connect(self.on_active_changed)
|
|
|
|
# video sink
|
|
# self.camera.setVideoOutput(self.video_widget)
|
|
self.capture_session.setCamera(self.camera)
|
|
self.capture_session.setVideoOutput(self.video_widget)
|
|
|
|
# przykładowe ustawienia
|
|
self.set_camera_format(
|
|
width=1280,
|
|
height=720,
|
|
fps=30,
|
|
preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg
|
|
)
|
|
|
|
self.log_runtime_camera_capabilities()
|
|
|
|
self.camera.start()
|
|
|
|
logger.info("Kamera uruchomiona.")
|
|
|
|
# ========================================================
|
|
# FORMAT SELECTION
|
|
# ========================================================
|
|
def set_camera_format(
|
|
self,
|
|
width: int,
|
|
height: int,
|
|
fps: int,
|
|
preferred_pixel_format=QVideoFrameFormat.PixelFormat.Format_Jpeg,
|
|
):
|
|
# def set_camera_format(self, width: int, height: int, fps: int):
|
|
"""
|
|
Prototyp funkcji ustawiającej:
|
|
- rozdzielczość
|
|
- FPS
|
|
|
|
Wybiera najbliższy pasujący format.
|
|
"""
|
|
|
|
logger.info(
|
|
f"Próba ustawienia formatu: {width}x{height} @ {fps} FPS"
|
|
)
|
|
|
|
best_match = None
|
|
|
|
for fmt in self.camera_device.videoFormats():
|
|
|
|
resolution = fmt.resolution()
|
|
min_fps = fmt.minFrameRate()
|
|
max_fps = fmt.maxFrameRate()
|
|
pixel_format = fmt.pixelFormat()
|
|
|
|
logger.debug(
|
|
f"Sprawdzam format -> "
|
|
f"{resolution.width()}x{resolution.height()} "
|
|
f"FPS:{min_fps}-{max_fps} "
|
|
f"PIX:{pixel_format}"
|
|
)
|
|
|
|
if (
|
|
resolution.width() == width
|
|
and resolution.height() == height
|
|
and min_fps <= fps <= max_fps
|
|
and pixel_format == preferred_pixel_format
|
|
):
|
|
best_match = fmt
|
|
break
|
|
|
|
if best_match:
|
|
self.camera.setCameraFormat(best_match)
|
|
|
|
logger.info("Ustawiono format kamery:")
|
|
logger.info(
|
|
f"Resolution: "
|
|
f"{best_match.resolution().width()}x"
|
|
f"{best_match.resolution().height()}"
|
|
)
|
|
logger.info(
|
|
f"FPS range: "
|
|
f"{best_match.minFrameRate()} - "
|
|
f"{best_match.maxFrameRate()}"
|
|
)
|
|
logger.info(
|
|
f"Pixel format: {best_match.pixelFormat()}"
|
|
)
|
|
|
|
else:
|
|
logger.warning("Nie znaleziono pasującego formatu.")
|
|
|
|
# ========================================================
|
|
# DEVICE LOGGER
|
|
# ========================================================
|
|
|
|
def log_camera_device(self, device: QCameraDevice, idx: int):
|
|
|
|
logger.info("------------------------------------------")
|
|
logger.info(f"KAMERA #{idx}")
|
|
logger.info("------------------------------------------")
|
|
|
|
logger.info(f"Description: {device.description()}")
|
|
logger.info(f"ID: {device.id().data().decode(errors='ignore')}")
|
|
|
|
try:
|
|
logger.info(f"Is default: {device.isDefault()}")
|
|
except Exception as e:
|
|
logger.warning(f"isDefault() unsupported: {e}")
|
|
|
|
formats = device.videoFormats()
|
|
|
|
logger.info(f"Liczba formatów: {len(formats)}")
|
|
|
|
for i, fmt in enumerate(formats):
|
|
|
|
resolution = fmt.resolution()
|
|
|
|
logger.info(f"")
|
|
logger.info(f"FORMAT #{i}")
|
|
|
|
logger.info(
|
|
f"Resolution: "
|
|
f"{resolution.width()}x{resolution.height()}"
|
|
)
|
|
|
|
logger.info(
|
|
f"FPS min/max: "
|
|
f"{fmt.minFrameRate()} / {fmt.maxFrameRate()}"
|
|
)
|
|
|
|
logger.info(
|
|
f"Pixel format enum: {fmt.pixelFormat()}"
|
|
)
|
|
|
|
logger.info(
|
|
f"Pixel format name: "
|
|
f"{self.pixel_format_to_string(fmt.pixelFormat())}"
|
|
)
|
|
|
|
# ========================================================
|
|
# RUNTIME CAMERA CAPABILITIES
|
|
# ========================================================
|
|
|
|
def log_runtime_camera_capabilities(self):
|
|
|
|
logger.info("")
|
|
logger.info("==========================================")
|
|
logger.info("QCAMERA RUNTIME CAPABILITIES")
|
|
logger.info("==========================================")
|
|
|
|
try:
|
|
logger.info(f"Camera active: {self.camera.isActive()}")
|
|
except Exception as e:
|
|
logger.warning(e)
|
|
|
|
# Dostępne w zależności od backendu/platformy
|
|
properties = [
|
|
"focusMode",
|
|
"exposureMode",
|
|
"whiteBalanceMode",
|
|
"flashMode",
|
|
"torchMode",
|
|
]
|
|
|
|
for prop in properties:
|
|
|
|
try:
|
|
value = getattr(self.camera, prop)()
|
|
logger.info(f"{prop}: {value}")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"{prop} unsupported: {e}")
|
|
|
|
# backend/platform info
|
|
logger.info("")
|
|
logger.info("QT MULTIMEDIA INFO")
|
|
|
|
try:
|
|
logger.info(f"Qt version: {QApplication.qtVersion()}")
|
|
except Exception:
|
|
pass
|
|
|
|
logger.info(
|
|
"Backend zależy od platformy:"
|
|
)
|
|
logger.info(
|
|
"- Windows -> MediaFoundation"
|
|
)
|
|
logger.info(
|
|
"- Linux -> GStreamer / PipeWire / V4L2"
|
|
)
|
|
logger.info(
|
|
"- macOS -> AVFoundation"
|
|
)
|
|
|
|
# ========================================================
|
|
# PIXEL FORMAT NAME
|
|
# ========================================================
|
|
|
|
def pixel_format_to_string(self, pixel_format):
|
|
|
|
mapping = {
|
|
QVideoFrameFormat.PixelFormat.Format_YUV420P: "YUV420P",
|
|
QVideoFrameFormat.PixelFormat.Format_NV12: "NV12",
|
|
QVideoFrameFormat.PixelFormat.Format_NV21: "NV21",
|
|
QVideoFrameFormat.PixelFormat.Format_UYVY: "UYVY",
|
|
QVideoFrameFormat.PixelFormat.Format_YUYV: "YUYV",
|
|
QVideoFrameFormat.PixelFormat.Format_Jpeg: "MJPG/JPEG",
|
|
}
|
|
|
|
return mapping.get(pixel_format, str(pixel_format))
|
|
|
|
# ========================================================
|
|
# SIGNALS
|
|
# ========================================================
|
|
|
|
def on_camera_error(self, error, error_string):
|
|
logger.error(f"CAMERA ERROR: {error}")
|
|
logger.error(f"ERROR STRING: {error_string}")
|
|
|
|
def on_active_changed(self, active):
|
|
logger.info(f"Camera active changed: {active}")
|
|
|
|
# ========================================================
|
|
# CLEANUP
|
|
# ========================================================
|
|
|
|
# def closeEvent(self, event):
|
|
|
|
# logger.info("Zamykanie aplikacji.")
|
|
|
|
# if self.camera:
|
|
# self.camera.stop()
|
|
|
|
# super().closeEvent(event)
|
|
|
|
def closeEvent(self, event):
|
|
|
|
logger.info("Zamykanie aplikacji.")
|
|
|
|
try:
|
|
if self.camera:
|
|
logger.info("Stopping camera...")
|
|
self.camera.stop()
|
|
|
|
if self.capture_session:
|
|
logger.info("Detaching camera from capture session...")
|
|
self.capture_session.setCamera(None)
|
|
self.capture_session.setVideoOutput(None)
|
|
|
|
except Exception as e:
|
|
logger.exception(e)
|
|
|
|
super().closeEvent(event)
|
|
|
|
# ============================================================
|
|
# MAIN
|
|
# ============================================================
|
|
|
|
def main():
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
window = CameraWindow()
|
|
|
|
# tylko obraz
|
|
window.resize(1280, 720)
|
|
|
|
# fullscreen:
|
|
# window.showFullScreen()
|
|
|
|
window.show()
|
|
|
|
sys.exit(app.exec())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |