initial commit

This commit is contained in:
Thies Mueller
2026-04-23 18:38:11 +02:00
commit 75537258a7
4 changed files with 332 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
.venv/
config.json
assets/logo.png
photos/
+25
View File
@@ -0,0 +1,25 @@
{
"ftp": {
"host": "ftp.example.com",
"user": "username",
"password": "password",
"base_url": "https://photobox.example.com/images/"
},
"printer_name": "DRUCKER",
"photo_path": "./photos/",
"buttons": {
"capture": "1",
"save": "2",
"print": "3"
},
"logo": {
"enabled": true,
"path": "assets/logo.png",
"position": "top_right",
"scale": 0.25,
"margin": 30
},
"camera": {
"last_used": null
}
}
+300
View File
@@ -0,0 +1,300 @@
import sys
import cv2
import json
import time
import os
from ftplib import FTP
from PyQt6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QVBoxLayout
from PyQt6.QtGui import QImage, QPixmap, QPainter, QFont, QColor
from PyQt6.QtCore import QTimer, Qt
import qrcode
CONFIG_PATH = "config.json"
def load_config():
with open(CONFIG_PATH) as f:
return json.load(f)
def save_config(cfg):
with open(CONFIG_PATH, "w") as f:
json.dump(cfg, f, indent=2)
CONFIG = load_config()
def create_capture(index):
backends = []
if sys.platform == "darwin":
backends = [
cv2.CAP_AVFOUNDATION,
cv2.CAP_ANY
]
elif os.name == "nt":
backends = [
cv2.CAP_DSHOW,
cv2.CAP_MSMF,
cv2.CAP_ANY
]
else:
backends = [
cv2.CAP_V4L2,
cv2.CAP_ANY
]
for backend in backends:
cap = cv2.VideoCapture(index, backend)
if not cap.isOpened():
continue
for _ in range(10):
ret, frame = cap.read()
if ret and frame is not None:
return cap
cap.release()
return None
def find_cameras():
working = []
for i in range(2):
cap = create_capture(i)
if cap is not None:
working.append(i)
cap.release()
return working
class CameraSelect(QWidget):
def __init__(self, cameras):
super().__init__()
self.setWindowTitle("Kamera auswählen")
self.setGeometry(200, 200, 400, 300)
self.selected = None
layout = QVBoxLayout()
for cam in cameras:
btn = QPushButton(f"Kamera {cam}")
btn.clicked.connect(lambda _, c=cam: self.select(c))
layout.addWidget(btn)
self.setLayout(layout)
def select(self, cam):
self.selected = cam
self.close()
LIVE, COUNTDOWN, PHOTO, QR = range(4)
class PhotoApp(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Photo Booth")
self.showFullScreen()
self.label = QLabel(self)
self.label.setGeometry(0, 0, 1920, 1080)
self.cap = None
self.init_camera()
self.state = LIVE
self.current_frame = None
self.photo = None
self.qr_img = None
self.countdown = 0
self.timer = QTimer()
self.timer.timeout.connect(self.update_frame)
self.timer.start(30)
self.countdown_timer = QTimer()
self.countdown_timer.timeout.connect(self.update_countdown)
def init_camera(self):
cams = find_cameras()
last = CONFIG.get("camera", {}).get("last_used")
if last in cams:
cam_index = last
else:
selector = CameraSelect(cams)
selector.show()
while selector.isVisible():
QApplication.processEvents()
cam_index = selector.selected
CONFIG["camera"]["last_used"] = cam_index
save_config(CONFIG)
self.cap = create_capture(cam_index)
if self.cap is None:
sys.exit(1)
for _ in range(10):
self.cap.read()
if not self.cap.isOpened():
sys.exit(1)
def update_frame(self):
if not self.cap:
return
ret, frame = self.cap.read()
if not ret or frame is None:
return
self.current_frame = frame
if self.state == LIVE:
self.draw(frame, "Drücke 1 für Foto")
elif self.state == COUNTDOWN:
self.draw(frame, str(self.countdown), big=True)
elif self.state == PHOTO:
self.draw(self.photo, "2=Speichern | 3=Drucken")
elif self.state == QR:
self.draw(self.qr_img, "QR Code scannen")
def draw(self, frame, text="", big=False):
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgb.shape
img = QImage(rgb.data, w, h, ch * w, QImage.Format.Format_RGB888)
pix = QPixmap.fromImage(img)
painter = QPainter(pix)
painter.fillRect(0, 900, 1920, 180, QColor(0, 0, 0, 120))
painter.setPen(Qt.GlobalColor.white)
if big:
painter.setFont(QFont("Arial", 200))
painter.drawText(pix.rect(), Qt.AlignmentFlag.AlignCenter, text)
else:
painter.setFont(QFont("Arial", 40))
painter.drawText(50, 1000, text)
if self.countdown > 0 and not big:
painter.drawText(1700, 1000, str(self.countdown))
painter.end()
self.label.setPixmap(pix)
def keyPressEvent(self, event):
key = event.text()
if key == CONFIG["buttons"]["capture"]:
self.start_countdown()
elif key == CONFIG["buttons"]["save"]:
self.save_photo()
elif key == CONFIG["buttons"]["print"]:
self.print_photo()
def start_countdown(self):
if self.state != LIVE:
return
self.state = COUNTDOWN
self.countdown = 3
self.countdown_timer.start(1000)
def take_photo(self):
self.photo = self.current_frame.copy()
self.state = PHOTO
self.countdown = 60
def save_photo(self):
if self.state != PHOTO:
return
url = self.upload(self.photo)
self.make_qr(url)
self.state = QR
self.start_qr_timer()
def print_photo(self):
if self.state != PHOTO:
return
url = self.upload(self.photo)
self.make_qr(url)
self.print_image()
self.state = QR
self.start_qr_timer()
def update_countdown(self):
self.countdown -= 1
if self.state == COUNTDOWN and self.countdown == 0:
self.take_photo()
elif self.countdown <= 0:
self.reset()
def start_qr_timer(self):
self.countdown = 30
self.countdown_timer.start(1000)
def reset(self):
self.state = LIVE
self.countdown = 0
self.photo = None
self.qr_img = None
self.countdown_timer.stop()
def upload(self, img):
filename = f"{int(time.time())}.jpg"
path = os.path.join(CONFIG["photo_path"], filename)
os.makedirs(CONFIG["photo_path"], exist_ok=True)
cv2.imwrite(path, img)
ftp = FTP(CONFIG["ftp"]["host"])
ftp.login(CONFIG["ftp"]["user"], CONFIG["ftp"]["password"])
with open(path, "rb") as f:
ftp.storbinary(f"STOR {filename}", f)
ftp.quit()
return CONFIG["ftp"]["base_url"] + filename
def make_qr(self, url):
img = qrcode.make(url)
img.save("qr.png")
self.qr_img = cv2.imread("qr.png")
def print_image(self):
temp = "print.jpg"
cv2.imwrite(temp, self.photo)
if os.name == "nt":
os.startfile(temp, "print")
else:
os.system(f"lp {temp}")
def closeEvent(self, event):
if self.cap:
self.cap.release()
cv2.destroyAllWindows()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
win = PhotoApp()
win.show()
sys.exit(app.exec())
+3
View File
@@ -0,0 +1,3 @@
opencv-python
pyqt6
qrcode