diff --git a/requirements.txt b/requirements.txt index a5594bb..e51be4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ PyQt6>=6.5 Pillow>=10.0 qrcode>=7.4 pycups>=2.0 +flask>=3.0 diff --git a/src/main.py b/src/main.py index 41ffaf1..340f1be 100644 --- a/src/main.py +++ b/src/main.py @@ -5,6 +5,7 @@ import sys from PyQt6.QtWidgets import QApplication from PyQt6.QtCore import Qt from ui.main_window import MainWindow +from services.upload_server import UploadServer def main(): @@ -14,8 +15,15 @@ def main(): window = MainWindow() window.setWindowFlags(Qt.WindowType.FramelessWindowHint) + # Serveur upload (WiFi / QR Code) + port = 8080 if "--no-root" in sys.argv else 80 + upload_server = UploadServer(port=port) + upload_server.signals.photos_received.connect( + window.home_screen.notify_qr_photos + ) + upload_server.start() + if "--kiosk" in sys.argv: - # Plein écran : prend toute la résolution de l'écran screen = app.primaryScreen().geometry() window.setGeometry(screen) window.showFullScreen() diff --git a/src/services/upload_server.py b/src/services/upload_server.py new file mode 100644 index 0000000..2761955 --- /dev/null +++ b/src/services/upload_server.py @@ -0,0 +1,327 @@ +"""Serveur Flask d'upload photos — tourne en thread à côté de PyQt6.""" + +import os +import threading +import time +from flask import Flask, request, jsonify, render_template_string +from PyQt6.QtCore import QObject, pyqtSignal + +UPLOAD_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "data", "photos") +ALLOWED_EXTENSIONS = {"jpg", "jpeg", "png", "bmp", "tiff", "tif", "webp", "heic"} + +UPLOAD_PAGE = """ + + + + +Photostation — Envoi de photos + + + +

Photostation

+

Sélectionnez vos photos à imprimer

+ +
+
📷
+
Appuyez ici pour choisir vos photos
ou glissez-les
+
+ + +
+ +
+
+
+ +
+ + + + + +""" + + +class UploadSignals(QObject): + """Signaux Qt pour communiquer du thread Flask vers le thread principal.""" + photos_received = pyqtSignal(list) # liste de chemins + + +class UploadServer: + """Serveur Flask d'upload, tourne dans un thread séparé.""" + + def __init__(self, port=80): + self.port = port + self.signals = UploadSignals() + self.app = Flask(__name__) + self._setup_routes() + self._thread = None + + os.makedirs(UPLOAD_DIR, exist_ok=True) + + def _setup_routes(self): + @self.app.route("/") + @self.app.route("/upload") + def index(): + return render_template_string(UPLOAD_PAGE) + + @self.app.route("/upload", methods=["POST"]) + def upload(): + files = request.files.getlist("photos") + saved = [] + for f in files: + if not f.filename: + continue + ext = f.filename.rsplit(".", 1)[-1].lower() if "." in f.filename else "" + if ext not in ALLOWED_EXTENSIONS: + continue + # Nom unique basé sur le timestamp + name = f"{int(time.time() * 1000)}_{f.filename}" + path = os.path.join(UPLOAD_DIR, name) + f.save(path) + saved.append(path) + + if saved: + self.signals.photos_received.emit(saved) + + return jsonify({"ok": True, "count": len(saved)}) + + def start(self): + """Démarre le serveur dans un thread daemon.""" + self._thread = threading.Thread( + target=lambda: self.app.run( + host="0.0.0.0", port=self.port, + debug=False, use_reloader=False + ), + daemon=True + ) + self._thread.start() + + def stop(self): + """Le thread daemon s'arrête avec le processus principal.""" + pass