refactor: enhance mock camera classes and update camera detection logic

This commit is contained in:
2025-10-09 18:47:17 +02:00
parent ca25b06f99
commit c815762f72
3 changed files with 119 additions and 17 deletions

View File

@@ -1,6 +1,16 @@
import cv2 import cv2
import numpy as np import numpy as np
GP_WIDGET_WINDOW = 0
GP_WIDGET_SECTION = 0
GP_WIDGET_TEXT = 0
GP_WIDGET_RANGE = 0
GP_WIDGET_TOGGLE = 0
GP_WIDGET_RADIO = 0
GP_WIDGET_MENU = 0
GP_WIDGET_BUTTON = 0
GP_WIDGET_DATE = 0
class GPhoto2Error(Exception): class GPhoto2Error(Exception):
pass pass
@@ -19,6 +29,65 @@ class CameraFileMock:
return self._data return self._data
return self._data, len(self._data) return self._data, len(self._data)
class CameraListMock:
def count(self):
return 1
def get_name(self, idx):
return f"mock_name {idx}"
def get_value(self, idx):
return f"mock_value {idx}"
class MockPortInfo:
def __init__(self, address):
self.address = address
class PortInfoList:
def __init__(self):
self._ports = []
def load(self):
# Dodaj przykładowe porty
self._ports = [MockPortInfo("usb:001,002"), MockPortInfo("usb:001,003")]
def lookup_path(self, port_address):
for idx, port in enumerate(self._ports):
if port.address == port_address:
return idx
raise ValueError("Port not found")
def __getitem__(self, idx):
return self._ports[idx]
class ConfigMock:
def get_id(self):
return 0
def get_name(self):
return "name"
def get_label(self):
return "label"
def get_type(self):
return 0
def get_value(self):
return "value"
def get_choices(self):
return []
def count_children(self):
return 0
def get_child(self):
return ConfigMock()
class CameraAbilitiesList:
def __init__(self) -> None:
self.abilities = []
def load(self):
return
def lookup_model(self, name):
return 1
def get_abilities(self, abilities_index):
return 0
class Camera: class Camera:
def __init__(self): def __init__(self):
@@ -62,3 +131,18 @@ class Camera:
self._frame_counter += 1 self._frame_counter += 1
return CameraFileMock(frame) return CameraFileMock(frame)
def set_port_info(self, obj):
return False
def get_config(self):
return ConfigMock()
def set_single_config(self, name, widget):
return True
def gp_camera_autodetect():
return CameraListMock()
def check_result(obj):
return obj

View File

@@ -1,11 +1,15 @@
from typing import Optional, List from typing import Optional, List
from dataclasses import dataclass, field from dataclasses import dataclass, field
import gphoto2 as gp
import cv2 import cv2
import numpy as np import numpy as np
from .base_camera import BaseCamera from .base_camera import BaseCamera
try:
import gphoto2 as gp # type: ignore
except:
import controllers.mock_gphoto as gp
camera_widget_types = { camera_widget_types = {
gp.GP_WIDGET_WINDOW: "GP_WIDGET_WINDOW", # type: ignore gp.GP_WIDGET_WINDOW: "GP_WIDGET_WINDOW", # type: ignore
gp.GP_WIDGET_SECTION: "GP_WIDGET_SECTION", # type: ignore gp.GP_WIDGET_SECTION: "GP_WIDGET_SECTION", # type: ignore
@@ -18,6 +22,16 @@ camera_widget_types = {
gp.GP_WIDGET_DATE: "GP_WIDGET_DATE", # type: ignore gp.GP_WIDGET_DATE: "GP_WIDGET_DATE", # type: ignore
} }
operations = [
("GP_OPERATION_NONE", gp.GP_OPERATION_NONE), # type: ignore
("GP_OPERATION_CAPTURE_IMAGE", gp.GP_OPERATION_CAPTURE_IMAGE), # type: ignore
("GP_OPERATION_CAPTURE_VIDEO", gp.GP_OPERATION_CAPTURE_VIDEO), # type: ignore
("GP_OPERATION_CAPTURE_AUDIO", gp.GP_OPERATION_CAPTURE_AUDIO), # type: ignore
("GP_OPERATION_CAPTURE_PREVIEW", gp.GP_OPERATION_CAPTURE_PREVIEW), # type: ignore
("GP_OPERATION_CONFIG", gp.GP_OPERATION_CONFIG), # type: ignore
("GP_OPERATION_TRIGGER_CAPTURE", gp.GP_OPERATION_TRIGGER_CAPTURE), # type: ignore
]
class GPhotoCamera(BaseCamera): class GPhotoCamera(BaseCamera):
def __init__(self) -> None: def __init__(self) -> None:
@@ -33,11 +47,21 @@ class GPhotoCamera(BaseCamera):
if not cameras or cameras.count() == 0: # type: ignore if not cameras or cameras.count() == 0: # type: ignore
return {} return {}
abilities_list = gp.CameraAbilitiesList() # type: ignore
abilities_list.load()
camera_list = {} camera_list = {}
for i in range(cameras.count()): # type: ignore for i in range(cameras.count()): # type: ignore
name = cameras.get_name(i) # type: ignore name = cameras.get_name(i) # type: ignore
port = cameras.get_value(i) # type: ignore port = cameras.get_value(i) # type: ignore
camera_list[i] = {"name": name, "port": port}
abilities_index = abilities_list.lookup_model(name)
abilities = abilities_list.get_abilities(abilities_index)
abilities_name = []
for name, bit in operations:
if abilities.operations & bit: # type: ignore
abilities_name.append(name)
camera_list[i] = {"name": name, "port": port, "abilities": abilities_name}
return camera_list return camera_list
def connect(self, index: int | None = None) -> bool: def connect(self, index: int | None = None) -> bool:
@@ -117,7 +141,7 @@ class GPhotoCamera(BaseCamera):
if not self.camera: if not self.camera:
return False return False
self.camera.set_single.config(config['name'], config['widget']) self.camera.set_single_config(config['name'], config['widget'])
return True return True
def parse_config(self, config): def parse_config(self, config):

View File

@@ -28,24 +28,18 @@ class OpenCvCamera(BaseCamera):
def detect(): def detect():
camera_list = enumerate_cameras(cv2.CAP_ANY) camera_list = enumerate_cameras(cv2.CAP_ANY)
result = {} result = {}
seen_ports = set()
for camera in camera_list: for camera in camera_list:
# unikamy duplikatów tego samego /dev/videoX albo tej samej ścieżki na Windows/macOS cap = cv2.VideoCapture(camera.index, camera.backend)
if camera.path in seen_ports: # ret, frame = cap.read()
continue
cap = cv2.VideoCapture(camera.index, camera.backend) # próbujemy otworzyć
ret, frame = cap.read()
cap.release() cap.release()
if ret and frame is not None and frame.size > 0: # if ret and frame is not None and frame.size > 0:
result[camera.index] = { result[camera.index] = {
"name": camera.name, "name": camera.name,
"port": camera.path, "port": camera.path,
"backend": camera.backend, "backend": camera.backend,
} }
seen_ports.add(camera.path)
return result return result