Add main application structure and logging configuration

- Implement main application entry point in duck-ocr.py
- Create logging configuration in logging_config.py
- Add video streaming functionality in camera.py
- Introduce main window UI in main_window.py
- Include SVG assets for UI buttons and icons
- Update .gitignore to exclude log files
- Add placeholder .gitkeep files for empty directories
This commit is contained in:
2026-05-10 13:00:12 +02:00
parent b095c36776
commit eaa7c75868
14 changed files with 458 additions and 0 deletions

188
app/main_window.py Normal file
View File

@@ -0,0 +1,188 @@
from enum import Enum
from typing import Any
import logging
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMainWindow, QStyle, QToolButton, QWidget, QVBoxLayout, QPushButton
from PySide6.QtGui import QIcon
from PySide6.QtCore import Qt, QSize
logger = logging.getLogger(__name__)
class VideoMode(Enum):
STREAMING = "streaming"
RECORDING = "recording"
PLAYING = "playing"
STOPPED = "stopped"
icons = {
"load_file":{
"path": "assets/folder-svgrepo-com.svg",
"tooltip": "Open File",
"size": QSize(48, 48)
},
"close_file": {
"path": "assets/close-square-svgrepo-com.svg",
"tooltip": "Close File",
"size": QSize(32, 32)
},
"record_on": {
"path": "assets/record_btn_on.svg",
"tooltip": "Start Recording",
"size": QSize(48, 48)
},
"record_off": {
"path": "assets/record_btn_off.svg",
"tooltip": "Stop Recording",
"size": QSize(48, 48)
},
"play": {
"path": "assets/play-svgrepo-com.svg",
"tooltip": "Play Video",
"size": QSize(48, 48)
},
"pause": {
"path": "assets/pause-svgrepo-com.svg",
"tooltip": "Pause Video",
"size": QSize(32, 32)
}
}
def set_icon(button: QToolButton, icon: str):
button.setIcon(QIcon(icons[icon]["path"]))
button.setToolTip(icons[icon]["tooltip"])
button.setIconSize(icons[icon]["size"])
def create_tool_button(icon: str) -> QToolButton:
button = QToolButton()
set_icon(button, icon)
button.setFixedSize(48, 48)
button.setStyleSheet("background-color: transparent; border: none;")
return button
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Duck Stain OCR")
self.resize(1280, 720)
self.video_mode = VideoMode.STREAMING
logger.debug(f"Initial video mode: {self.video_mode}")
self.setup_ui()
def setup_ui(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.central_widget.setStyleSheet("background-color: #001e1e;")
self.toolbar_widget = QWidget(self.central_widget)
self.toolbar_widget.setMinimumWidth(400)
self.toolbar_widget.setObjectName("bottomToolbar")
self.toolbar_widget.setStyleSheet(
"""
QWidget#bottomToolbar {
background: rgba(0, 0, 0, 0.1);
border-radius: 18px;
}
"""
)
left_layout = QHBoxLayout()
right_layout = QHBoxLayout()
self.toolbar_layout = QGridLayout(self.toolbar_widget)
self.toolbar_layout.setContentsMargins(18, 16, 18, 16)
self.toolbar_layout.setColumnStretch(0, 1)
self.toolbar_layout.setColumnStretch(1, 0)
self.toolbar_layout.setColumnStretch(2, 1)
self.toolbar_layout.addLayout(left_layout, 0, 0)
self.toolbar_layout.addLayout(right_layout, 0, 2)
# Add buttons to the left layout
self.open_file_button = create_tool_button("load_file")
self.open_file_button.clicked.connect(self.load_video)
left_layout.addWidget(self.open_file_button)
self.close_file_button = create_tool_button("close_file")
self.close_file_button.clicked.connect(self.close_video)
self.close_file_button.setEnabled(False)
left_layout.addWidget(self.close_file_button)
left_layout.addStretch()
# Add the record button to the center of the toolbar
self.action_button = create_tool_button("record_off")
self.action_button.clicked.connect(self.toggle_action)
self.toolbar_layout.addWidget(self.action_button, 0, 1, alignment=Qt.AlignmentFlag.AlignCenter)
# Add buttons to the right layout
right_layout.addStretch()
def resizeEvent(self, event: Any) -> None:
super().resizeEvent(event)
self.toolbar_widget.adjustSize()
toolbar_size = self.toolbar_widget.sizeHint()
self.toolbar_widget.setGeometry(
(self.central_widget.width() - self.toolbar_widget.width()) // 2,
self.central_widget.height() - self.toolbar_widget.height() - 80,
self.toolbar_widget.width(),
self.toolbar_widget.height(),
)
self.toolbar_widget.raise_() # Ensure the toolbar is above other widgets
def toggle_action(self):
match self.video_mode:
case VideoMode.STREAMING:
self.start_recording()
case VideoMode.RECORDING:
self.stop_recording()
case VideoMode.PLAYING:
self.pause_video()
case VideoMode.STOPPED:
self.play_video()
case _:
pass
def start_recording(self):
logger.debug("Starting recording")
self.video_mode = VideoMode.RECORDING
set_icon(self.action_button, "record_on")
self.open_file_button.setEnabled(False)
self.close_file_button.setEnabled(False)
def stop_recording(self):
logger.debug("Stopping recording")
self.video_mode = VideoMode.STREAMING
set_icon(self.action_button, "record_off")
self.open_file_button.setEnabled(True)
self.close_file_button.setEnabled(False)
def load_video(self):
logger.debug("Loading video")
self.video_mode = VideoMode.STOPPED
set_icon(self.action_button, "play")
self.close_file_button.setEnabled(True)
def close_video(self):
logger.debug("Closing video")
self.video_mode = VideoMode.STREAMING
set_icon(self.action_button, "record_off")
self.close_file_button.setEnabled(False)
def play_video(self):
logger.debug("Playing video")
self.video_mode = VideoMode.PLAYING
set_icon(self.action_button, "pause")
def pause_video(self):
logger.debug("Pausing video")
self.video_mode = VideoMode.STOPPED
set_icon(self.action_button, "play")