i hate python

This commit is contained in:
rattatwinko
2025-05-26 16:39:58 +02:00
parent b24426c1d3
commit c264acac29

View File

@@ -10,16 +10,25 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout
QActionGroup, QSizePolicy, QGridLayout, QGroupBox, QActionGroup, QSizePolicy, QGridLayout, QGroupBox,
QDockWidget, QScrollArea, QToolButton, QDialog, QDockWidget, QScrollArea, QToolButton, QDialog,
QShortcut, QListWidget, QFormLayout, QLineEdit, QShortcut, QListWidget, QFormLayout, QLineEdit,
QCheckBox, QTabWidget, QListWidgetItem) QCheckBox, QTabWidget, QListWidgetItem, QSplitter)
from PyQt5.QtCore import Qt, QTimer, QDir, QSize, QSettings, QDateTime, QRect from PyQt5.QtCore import Qt, QTimer, QDir, QSize, QSettings, QDateTime, QRect
from PyQt5.QtGui import (QImage, QPixmap, QIcon, QColor, QKeySequence, QPainter, from PyQt5.QtGui import (QImage, QPixmap, QIcon, QColor, QKeySequence, QPainter,
QPen, QBrush) QPen, QBrush)
class Config: class Config:
def __init__(self): def __init__(self):
# Use JSON file in current working directory instead of QSettings # Use JSON file in current working directory
self.config_file = os.path.join(os.getcwd(), 'mucapy_config.json') self.config_file = os.path.join(os.getcwd(), 'mucapy_config.json')
self.settings = {} self.settings = {
'network_cameras': {}, # Store network cameras configuration
'last_model_dir': '',
'last_screenshot_dir': os.path.expanduser('~/Pictures/MuCaPy'),
'last_layout': 0,
'last_fps': 10,
'last_selected_cameras': [],
'window_geometry': None,
'confidence_threshold': 0.35,
}
self.load_config() self.load_config()
def load_config(self): def load_config(self):
@@ -27,14 +36,17 @@ class Config:
try: try:
if os.path.exists(self.config_file): if os.path.exists(self.config_file):
with open(self.config_file, 'r') as f: with open(self.config_file, 'r') as f:
self.settings = json.load(f) loaded_settings = json.load(f)
# Update settings while preserving default values for new keys
self.settings.update(loaded_settings)
except Exception as e: except Exception as e:
print(f"Error loading config: {e}") print(f"Error loading config: {e}")
self.settings = {}
def save_config(self): def save_config(self):
"""Save configuration to JSON file""" """Save configuration to JSON file"""
try: try:
# Ensure the file's directory exists
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
with open(self.config_file, 'w') as f: with open(self.config_file, 'w') as f:
json.dump(self.settings, f, indent=4) json.dump(self.settings, f, indent=4)
except Exception as e: except Exception as e:
@@ -61,12 +73,19 @@ class MultiCamYOLODetector:
self.available_cameras = [] self.available_cameras = []
self.model_dir = "" self.model_dir = ""
self.cuda_available = self.check_cuda() self.cuda_available = self.check_cuda()
self.confidence_threshold = 0.35
self.config = Config() self.config = Config()
# Load saved network cameras # Load settings
self.confidence_threshold = self.config.load_setting('confidence_threshold', 0.35)
self.network_cameras = self.config.load_setting('network_cameras', {}) self.network_cameras = self.config.load_setting('network_cameras', {})
self.target_fps = self.config.load_setting('last_fps', 10)
self.frame_interval = 1.0 / self.target_fps
# Load last used model if available
last_model = self.config.load_setting('last_model_dir')
if last_model and os.path.exists(last_model):
self.load_yolo_model(last_model)
def check_cuda(self): def check_cuda(self):
"""Check if CUDA is available""" """Check if CUDA is available"""
try: try:
@@ -78,13 +97,17 @@ class MultiCamYOLODetector:
def add_network_camera(self, name, camera_info): def add_network_camera(self, name, camera_info):
"""Add a network camera to the saved list""" """Add a network camera to the saved list"""
self.network_cameras[name] = camera_info self.network_cameras[name] = camera_info
self.config.save_setting('network_cameras', self.network_cameras) # Save to configuration immediately
self.config.settings['network_cameras'] = self.network_cameras
self.config.save_config()
def remove_network_camera(self, name): def remove_network_camera(self, name):
"""Remove a network camera from the saved list""" """Remove a network camera from the saved list"""
if name in self.network_cameras: if name in self.network_cameras:
del self.network_cameras[name] del self.network_cameras[name]
self.config.save_setting('network_cameras', self.network_cameras) # Save to configuration immediately
self.config.settings['network_cameras'] = self.network_cameras
self.config.save_config()
def scan_for_cameras(self, max_to_check=10): def scan_for_cameras(self, max_to_check=10):
"""Check for available cameras including network cameras""" """Check for available cameras including network cameras"""
@@ -725,7 +748,7 @@ class CameraSelectorDialog(QDialog):
super().__init__(parent) super().__init__(parent)
self.setWindowTitle("Camera Selector") self.setWindowTitle("Camera Selector")
self.setModal(True) self.setModal(True)
self.resize(600, 500) self.resize(800, 600) # Increased size for better visibility
self.detector = parent.detector if parent else None self.detector = parent.detector if parent else None
self.selected_cameras = [] self.selected_cameras = []
@@ -733,183 +756,321 @@ class CameraSelectorDialog(QDialog):
# Main layout # Main layout
layout = QVBoxLayout(self) layout = QVBoxLayout(self)
# Create tab widget # Instructions with better formatting
tabs = QTabWidget() instructions = QLabel(
"Camera Selection Guide:\n\n"
"• Local Cameras: Built-in and USB cameras\n"
"• Network Cameras: IP cameras, DroidCam, etc.\n"
"• Use checkboxes to select/deselect cameras\n"
"• Double-click a camera to test the connection\n"
"• Selected cameras will appear in the preview below"
)
instructions.setStyleSheet("QLabel { background-color: #2A2A2A; padding: 10px; border-radius: 4px; }")
instructions.setWordWrap(True)
layout.addWidget(instructions)
# Local Cameras Tab # Split view for cameras
local_tab = QWidget() splitter = QSplitter(Qt.Horizontal)
local_layout = QVBoxLayout(local_tab)
# Local camera list # Left side - Available Cameras
left_widget = QWidget()
left_layout = QVBoxLayout(left_widget)
# Local Cameras Group
local_group = QGroupBox("Local Cameras") local_group = QGroupBox("Local Cameras")
local_list_layout = QVBoxLayout() local_layout = QVBoxLayout()
self.local_list = QListWidget() self.local_list = QListWidget()
self.local_list.setSelectionMode(QListWidget.MultiSelection) self.local_list.setSelectionMode(QListWidget.ExtendedSelection)
local_list_layout.addWidget(self.local_list) local_layout.addWidget(self.local_list)
local_group.setLayout(local_layout)
left_layout.addWidget(local_group)
# Refresh button for local cameras # Network Cameras Group
refresh_btn = QPushButton("Refresh Local Cameras")
refresh_btn.clicked.connect(self.refresh_local_cameras)
local_list_layout.addWidget(refresh_btn)
local_group.setLayout(local_list_layout)
local_layout.addWidget(local_group)
tabs.addTab(local_tab, "Local Cameras")
# Network Cameras Tab
network_tab = QWidget()
network_layout = QVBoxLayout(network_tab)
# Network camera list
network_group = QGroupBox("Network Cameras") network_group = QGroupBox("Network Cameras")
network_list_layout = QVBoxLayout() network_layout = QVBoxLayout()
self.network_list = QListWidget() self.network_list = QListWidget()
self.network_list.setSelectionMode(QListWidget.MultiSelection) self.network_list.setSelectionMode(QListWidget.ExtendedSelection)
network_list_layout.addWidget(self.network_list) network_layout.addWidget(self.network_list)
network_group.setLayout(network_layout)
left_layout.addWidget(network_group)
# Network camera controls # Camera management buttons
net_btn_layout = QHBoxLayout() btn_layout = QHBoxLayout()
refresh_btn = QPushButton("Refresh")
refresh_btn.clicked.connect(self.refresh_cameras)
add_net_btn = QPushButton("Add Network Camera") add_net_btn = QPushButton("Add Network Camera")
add_net_btn.clicked.connect(self.show_network_dialog) add_net_btn.clicked.connect(self.show_network_dialog)
remove_net_btn = QPushButton("Remove Selected")
remove_net_btn.clicked.connect(self.remove_network_camera)
net_btn_layout.addWidget(add_net_btn)
net_btn_layout.addWidget(remove_net_btn)
network_list_layout.addLayout(net_btn_layout)
network_group.setLayout(network_list_layout) btn_layout.addWidget(refresh_btn)
network_layout.addWidget(network_group) btn_layout.addWidget(add_net_btn)
tabs.addTab(network_tab, "Network Cameras") left_layout.addLayout(btn_layout)
layout.addWidget(tabs) splitter.addWidget(left_widget)
# Right side - Selected Cameras Preview
right_widget = QWidget()
right_layout = QVBoxLayout(right_widget)
preview_label = QLabel("Selected Cameras Preview")
preview_label.setStyleSheet("font-weight: bold;")
right_layout.addWidget(preview_label)
# Selected cameras preview
preview_group = QGroupBox("Selected Cameras")
preview_layout = QVBoxLayout()
self.preview_list = QListWidget() self.preview_list = QListWidget()
preview_layout.addWidget(self.preview_list) self.preview_list.setDragDropMode(QListWidget.InternalMove)
preview_group.setLayout(preview_layout) self.preview_list.setSelectionMode(QListWidget.ExtendedSelection)
layout.addWidget(preview_group) right_layout.addWidget(self.preview_list)
# Buttons # Preview controls
btn_layout = QHBoxLayout() preview_btn_layout = QHBoxLayout()
remove_btn = QPushButton("Remove Selected")
remove_btn.clicked.connect(self.remove_selected)
clear_btn = QPushButton("Clear All")
clear_btn.clicked.connect(self.clear_selection)
preview_btn_layout.addWidget(remove_btn)
preview_btn_layout.addWidget(clear_btn)
right_layout.addLayout(preview_btn_layout)
splitter.addWidget(right_widget)
layout.addWidget(splitter)
# Bottom buttons
bottom_layout = QHBoxLayout()
select_all_btn = QPushButton("Select All") select_all_btn = QPushButton("Select All")
select_all_btn.clicked.connect(self.select_all) select_all_btn.clicked.connect(self.select_all)
clear_btn = QPushButton("Clear Selection") test_selected_btn = QPushButton("Test Selected")
clear_btn.clicked.connect(self.clear_selection) test_selected_btn.clicked.connect(self.test_selected_cameras)
ok_btn = QPushButton("OK") ok_btn = QPushButton("OK")
ok_btn.clicked.connect(self.accept) ok_btn.clicked.connect(self.accept)
cancel_btn = QPushButton("Cancel") cancel_btn = QPushButton("Cancel")
cancel_btn.clicked.connect(self.reject) cancel_btn.clicked.connect(self.reject)
btn_layout.addWidget(select_all_btn) bottom_layout.addWidget(select_all_btn)
btn_layout.addWidget(clear_btn) bottom_layout.addWidget(test_selected_btn)
btn_layout.addStretch() bottom_layout.addStretch()
btn_layout.addWidget(ok_btn) bottom_layout.addWidget(ok_btn)
btn_layout.addWidget(cancel_btn) bottom_layout.addWidget(cancel_btn)
layout.addLayout(btn_layout) layout.addLayout(bottom_layout)
# Connect selection change signals # Connect signals
self.local_list.itemSelectionChanged.connect(self.update_preview) self.local_list.itemChanged.connect(self.update_preview)
self.network_list.itemSelectionChanged.connect(self.update_preview) self.network_list.itemChanged.connect(self.update_preview)
self.preview_list.model().rowsMoved.connect(self.update_camera_order)
# Set splitter sizes
splitter.setSizes([400, 400])
# Initial camera refresh
self.refresh_cameras() self.refresh_cameras()
# Restore last selection if available
if self.detector:
last_selected = self.detector.config.load_setting('last_selected_cameras', [])
if last_selected:
self.restore_selection(last_selected)
def refresh_cameras(self): def refresh_cameras(self):
"""Refresh both local and network camera lists""" """Refresh both local and network camera lists"""
self.refresh_local_cameras()
self.refresh_network_cameras()
def refresh_local_cameras(self):
"""Refresh the local camera list"""
self.local_list.clear() self.local_list.clear()
if self.detector:
# Get local cameras
for i in range(10): # Check first 10 indices
try:
cap = cv2.VideoCapture(i)
if cap.isOpened():
self.local_list.addItem(QListWidgetItem(f"Camera {i}"))
cap.release()
except:
continue
# Check device paths
if os.path.exists('/dev'):
for i in range(10):
device_path = f"/dev/video{i}"
if os.path.exists(device_path):
try:
cap = cv2.VideoCapture(device_path)
if cap.isOpened():
item = QListWidgetItem(os.path.basename(device_path))
item.setData(Qt.UserRole, device_path)
self.local_list.addItem(item)
cap.release()
except:
continue
def refresh_network_cameras(self):
"""Refresh the network camera list"""
self.network_list.clear() self.network_list.clear()
if self.detector:
for name, camera_info in self.detector.network_cameras.items(): if not self.detector:
if isinstance(camera_info, dict): return
url = camera_info.get('url', '')
has_auth = camera_info.get('username') is not None # Add local cameras
display_text = f"{name} ({url})" for i in range(10): # Check first 10 indices
if has_auth: try:
display_text += " [Auth]" cap = cv2.VideoCapture(i)
else: if cap.isOpened():
display_text = f"{name} ({camera_info})" item = QListWidgetItem(f"Camera {i}")
item.setData(Qt.UserRole, str(i))
item = QListWidgetItem(display_text) item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setData(Qt.UserRole, f"net:{name}") item.setCheckState(Qt.Unchecked)
self.network_list.addItem(item) self.local_list.addItem(item)
cap.release()
except Exception as e:
print(f"Error checking camera {i}: {e}")
# Check device paths
if os.path.exists('/dev'):
for i in range(10):
device_path = f"/dev/video{i}"
if os.path.exists(device_path):
try:
cap = cv2.VideoCapture(device_path)
if cap.isOpened():
item = QListWidgetItem(f"{os.path.basename(device_path)}")
item.setData(Qt.UserRole, device_path)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Unchecked)
self.local_list.addItem(item)
cap.release()
except Exception as e:
print(f"Error checking device {device_path}: {e}")
# Add network cameras
for name, camera_info in self.detector.network_cameras.items():
if isinstance(camera_info, dict):
url = camera_info.get('url', '')
has_auth = camera_info.get('username') is not None
display_text = f"{name} ({url})"
if has_auth:
display_text += " 🔒"
else:
display_text = f"{name} ({camera_info})"
item = QListWidgetItem(display_text)
item.setData(Qt.UserRole, f"net:{name}")
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Unchecked)
self.network_list.addItem(item)
def show_network_dialog(self): def restore_selection(self, last_selected):
"""Show the network camera configuration dialog""" """Restore previous camera selection"""
dialog = NetworkCameraDialog(self.parent()) for cam_id in last_selected:
if dialog.exec_() == QDialog.Accepted: # Check local cameras
self.refresh_network_cameras() for i in range(self.local_list.count()):
item = self.local_list.item(i)
def remove_network_camera(self): if item.data(Qt.UserRole) == cam_id:
"""Remove selected network cameras""" item.setCheckState(Qt.Checked)
for item in self.network_list.selectedItems():
cam_path = item.data(Qt.UserRole) # Check network cameras
if cam_path.startswith('net:'): for i in range(self.network_list.count()):
name = cam_path[4:] item = self.network_list.item(i)
if self.detector: if item.data(Qt.UserRole) == cam_id:
self.detector.remove_network_camera(name) item.setCheckState(Qt.Checked)
self.refresh_network_cameras()
self.update_preview()
def update_preview(self): def update_preview(self):
"""Update the preview list with currently selected cameras""" """Update the preview list with currently selected cameras"""
self.preview_list.clear() self.preview_list.clear()
self.selected_cameras = [] self.selected_cameras = []
# Add selected local cameras # Get selected local cameras
for item in self.local_list.selectedItems(): for i in range(self.local_list.count()):
cam_path = item.data(Qt.UserRole) if item.data(Qt.UserRole) else item.text().split()[-1] item = self.local_list.item(i)
self.selected_cameras.append(cam_path) if item.checkState() == Qt.Checked:
self.preview_list.addItem(f"{item.text()}") cam_id = item.data(Qt.UserRole)
preview_item = QListWidgetItem(f"📷 {item.text()}")
preview_item.setData(Qt.UserRole, cam_id)
self.preview_list.addItem(preview_item)
self.selected_cameras.append(cam_id)
# Add selected network cameras # Get selected network cameras
for item in self.network_list.selectedItems(): for i in range(self.network_list.count()):
cam_path = item.data(Qt.UserRole) item = self.network_list.item(i)
self.selected_cameras.append(cam_path) if item.checkState() == Qt.Checked:
self.preview_list.addItem(f"{item.text()}") cam_id = item.data(Qt.UserRole)
preview_item = QListWidgetItem(f"🌐 {item.text()}")
preview_item.setData(Qt.UserRole, cam_id)
self.preview_list.addItem(preview_item)
self.selected_cameras.append(cam_id)
# Save the current selection to config
if self.detector:
self.detector.config.save_setting('last_selected_cameras', self.selected_cameras)
def update_camera_order(self):
"""Update the camera order based on preview list order"""
self.selected_cameras = []
for i in range(self.preview_list.count()):
item = self.preview_list.item(i)
self.selected_cameras.append(item.data(Qt.UserRole))
# Save the new order
if self.detector:
self.detector.config.save_setting('last_selected_cameras', self.selected_cameras)
def select_all(self): def select_all(self):
"""Select all cameras in both lists""" """Select all cameras in both lists"""
self.local_list.selectAll() for i in range(self.local_list.count()):
self.network_list.selectAll() self.local_list.item(i).setCheckState(Qt.Checked)
for i in range(self.network_list.count()):
self.network_list.item(i).setCheckState(Qt.Checked)
def clear_selection(self): def clear_selection(self):
"""Clear selection in both lists""" """Clear all selections"""
self.local_list.clearSelection() for i in range(self.local_list.count()):
self.network_list.clearSelection() self.local_list.item(i).setCheckState(Qt.Unchecked)
for i in range(self.network_list.count()):
self.network_list.item(i).setCheckState(Qt.Unchecked)
def remove_selected(self):
"""Remove selected items from the preview list"""
selected_items = self.preview_list.selectedItems()
for item in selected_items:
cam_id = item.data(Qt.UserRole)
# Uncheck corresponding items in source lists
for i in range(self.local_list.count()):
if self.local_list.item(i).data(Qt.UserRole) == cam_id:
self.local_list.item(i).setCheckState(Qt.Unchecked)
for i in range(self.network_list.count()):
if self.network_list.item(i).data(Qt.UserRole) == cam_id:
self.network_list.item(i).setCheckState(Qt.Unchecked)
def test_selected_cameras(self):
"""Test connection to selected cameras"""
selected_cameras = []
for i in range(self.preview_list.count()):
selected_cameras.append(self.preview_list.item(i).data(Qt.UserRole))
if not selected_cameras:
QMessageBox.warning(self, "Warning", "No cameras selected to test!")
return
# Create progress dialog
progress = QMessageBox(self)
progress.setIcon(QMessageBox.Information)
progress.setWindowTitle("Testing Cameras")
progress.setText("Testing camera connections...\nThis may take a few seconds.")
progress.setStandardButtons(QMessageBox.NoButton)
progress.show()
QApplication.processEvents()
# Test each camera
results = []
for cam_id in selected_cameras:
try:
if cam_id.startswith('net:'):
name = cam_id[4:]
camera_info = self.detector.network_cameras.get(name)
if isinstance(camera_info, dict):
url = camera_info['url']
if 'username' in camera_info and 'password' in camera_info:
parsed = urllib.parse.urlparse(url)
netloc = f"{camera_info['username']}:{camera_info['password']}@{parsed.netloc}"
url = parsed._replace(netloc=netloc).geturl()
else:
url = camera_info
cap = cv2.VideoCapture(url)
else:
cap = cv2.VideoCapture(int(cam_id) if cam_id.isdigit() else cam_id)
if cap.isOpened():
ret, frame = cap.read()
if ret:
results.append(f"{cam_id}: Connection successful")
else:
results.append(f"⚠️ {cam_id}: Connected but no frame received")
else:
results.append(f"{cam_id}: Failed to connect")
cap.release()
except Exception as e:
results.append(f"{cam_id}: Error - {str(e)}")
progress.close()
# Show results
result_dialog = QMessageBox(self)
result_dialog.setWindowTitle("Camera Test Results")
result_dialog.setText("\n".join(results))
result_dialog.setIcon(QMessageBox.Information)
result_dialog.exec_()
def show_network_dialog(self):
"""Show the network camera configuration dialog"""
dialog = NetworkCameraDialog(self)
if dialog.exec_() == QDialog.Accepted:
self.refresh_cameras()
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self): def __init__(self):