Galerie live booth.copydev.fr + icones agrandies + pellicule

- booth/: serveur Node.js galerie photo live avec auth par code 4 chiffres
- Backend: envoi auto des photos vers booth, endpoint QR dynamique
- Frontend: code + QR booth sur ecran accueil, icone pellicule pour multi-shot
- Icones mode agrandies (5rem)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 17:27:05 +02:00
parent e583126f85
commit f0584e8fdf
8 changed files with 835 additions and 4 deletions

View File

@@ -1,7 +1,10 @@
import ftplib
import logging
import shutil
import threading
from pathlib import Path
from urllib.request import Request, urlopen
from urllib.error import URLError
from backend.config import charger_config, mettre_a_jour_config
@@ -26,6 +29,15 @@ def distribuer_photo(chemin_photo: Path, imprimee: bool = False):
if dest.get("ftp", False):
envoyer_ftp(chemin_photo, dest)
# Galerie live (booth)
booth = config.get("booth", {})
if booth.get("actif", False):
threading.Thread(
target=envoyer_booth,
args=(chemin_photo, booth),
daemon=True,
).start()
# Incrementer le compteur
compteur = config.get("compteur", {})
if compteur.get("actif", False):
@@ -83,6 +95,73 @@ def envoyer_ftp(chemin_photo: Path, config_dest: dict):
return False
def envoyer_booth(chemin_photo: Path, config_booth: dict):
"""Envoie une photo vers la galerie live booth."""
url = config_booth.get("url", "").rstrip("/")
api_key = config_booth.get("api_key", "")
event_id = config_booth.get("event_id", "default")
if not url:
log.warning("Booth non configure (URL manquante)")
return False
try:
import mimetypes
boundary = "----BoothUpload"
filename = chemin_photo.name
with open(chemin_photo, "rb") as f:
file_data = f.read()
body = (
f"--{boundary}\r\n"
f'Content-Disposition: form-data; name="photo"; filename="{filename}"\r\n'
f"Content-Type: image/jpeg\r\n\r\n"
).encode() + file_data + f"\r\n--{boundary}--\r\n".encode()
req = Request(
f"{url}/api/{event_id}/upload",
data=body,
method="POST",
)
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
req.add_header("X-Api-Key", api_key)
with urlopen(req, timeout=10) as resp:
if resp.status == 200:
log.info(f"Photo envoyee au booth : {filename}")
return True
else:
log.error(f"Booth erreur HTTP {resp.status}")
return False
except (URLError, OSError) as e:
log.error(f"Erreur envoi booth : {e}")
return False
def recuperer_booth_password() -> dict:
"""Recupere le mot de passe et l'info de la session booth."""
config = charger_config()
booth = config.get("booth", {})
url = booth.get("url", "").rstrip("/")
api_key = booth.get("api_key", "")
event_id = booth.get("event_id", "default")
if not url:
return {"password": None}
try:
req = Request(f"{url}/api/{event_id}/info")
req.add_header("X-Api-Key", api_key)
with urlopen(req, timeout=5) as resp:
import json
data = json.loads(resp.read())
return data
except (URLError, OSError) as e:
log.error(f"Erreur recuperation booth info : {e}")
return {"password": None}
def detecter_usb() -> list[str]:
"""Detecte les cles USB montees."""
chemins_possibles = [Path("/media"), Path("/mnt")]

View File

@@ -17,7 +17,7 @@ from backend.camera import camera
from backend.gallery import lister_photos, compter_photos, supprimer_photo, vider_galerie
from backend.effects import appliquer_filtre, appliquer_overlay, chroma_key, lister_overlays, lister_fonds, FILTRES
from backend.collage import creer_strip, creer_collage, creer_impression_strip
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur, recuperer_booth_password
from backend.printer import lister_imprimantes, imprimer
from backend.mailer import envoyer_photo
from backend.qrcode_gen import generer_qr, qr_galerie
@@ -243,6 +243,13 @@ async def api_imprimer(donnees: dict):
return {"succes": ok}
# --- API Booth (galerie live) ---
@app.get("/api/booth/info")
async def api_booth_info():
return recuperer_booth_password()
# --- API Email ---
@app.post("/api/email")
@@ -262,6 +269,21 @@ async def api_email(donnees: dict):
# --- API QR Code ---
@app.get("/api/qr")
async def api_qr(url: str = ""):
"""Genere un QR code pour une URL arbitraire."""
if not url:
return JSONResponse({"erreur": "URL requise"}, status_code=400)
import qrcode
import io
qr = qrcode.make(url, box_size=6, border=2)
buf = io.BytesIO()
qr.save(buf, format="PNG")
buf.seek(0)
from fastapi.responses import StreamingResponse
return StreamingResponse(buf, media_type="image/png")
@app.get("/api/qr/galerie")
async def api_qr_galerie():
chemin = qr_galerie()