initial commit
This commit is contained in:
@@ -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())
|
||||
Reference in New Issue
Block a user