- 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>
194 lines
6.0 KiB
Python
194 lines
6.0 KiB
Python
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
|
|
|
|
log = logging.getLogger("photobooth.destinations")
|
|
|
|
|
|
def distribuer_photo(chemin_photo: Path, imprimee: bool = False):
|
|
"""Copie la photo vers toutes les destinations activees."""
|
|
config = charger_config()
|
|
dest = config.get("destinations", {})
|
|
|
|
# Verifier si on sauvegarde tout ou seulement les imprimees
|
|
if not dest.get("sauvegarder_tout", True) and not imprimee:
|
|
log.info(f"Photo non imprimee, pas de distribution : {chemin_photo.name}")
|
|
return
|
|
|
|
# Cle USB
|
|
if dest.get("cle_usb", False):
|
|
copier_usb(chemin_photo, dest.get("chemin_usb", "/media/usb"))
|
|
|
|
# FTP
|
|
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):
|
|
compteur["photos_prises"] = compteur.get("photos_prises", 0) + 1
|
|
mettre_a_jour_config({"compteur": compteur})
|
|
|
|
|
|
def copier_usb(chemin_photo: Path, chemin_usb: str):
|
|
"""Copie une photo sur la cle USB."""
|
|
dossier_usb = Path(chemin_usb)
|
|
if not dossier_usb.exists():
|
|
log.warning(f"Cle USB non trouvee : {chemin_usb}")
|
|
return False
|
|
|
|
dossier_dest = dossier_usb / "photobooth"
|
|
dossier_dest.mkdir(exist_ok=True)
|
|
|
|
try:
|
|
shutil.copy2(chemin_photo, dossier_dest / chemin_photo.name)
|
|
log.info(f"Photo copiee sur USB : {chemin_photo.name}")
|
|
return True
|
|
except OSError as e:
|
|
log.error(f"Erreur copie USB : {e}")
|
|
return False
|
|
|
|
|
|
def envoyer_ftp(chemin_photo: Path, config_dest: dict):
|
|
"""Envoie une photo par FTP."""
|
|
host = config_dest.get("ftp_host", "")
|
|
port = config_dest.get("ftp_port", 21)
|
|
user = config_dest.get("ftp_user", "")
|
|
password = config_dest.get("ftp_password", "")
|
|
chemin_distant = config_dest.get("ftp_chemin", "/photobooth")
|
|
|
|
if not host:
|
|
log.warning("FTP non configure")
|
|
return False
|
|
|
|
try:
|
|
with ftplib.FTP() as ftp:
|
|
ftp.connect(host, port, timeout=10)
|
|
ftp.login(user, password)
|
|
# Creer le dossier si necessaire
|
|
try:
|
|
ftp.mkd(chemin_distant)
|
|
except ftplib.error_perm:
|
|
pass
|
|
ftp.cwd(chemin_distant)
|
|
with open(chemin_photo, "rb") as f:
|
|
ftp.storbinary(f"STOR {chemin_photo.name}", f)
|
|
log.info(f"Photo envoyee par FTP : {chemin_photo.name}")
|
|
return True
|
|
except (ftplib.all_errors, OSError) as e:
|
|
log.error(f"Erreur FTP : {e}")
|
|
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")]
|
|
usb_trouvees = []
|
|
for racine in chemins_possibles:
|
|
if racine.exists():
|
|
for sous_dossier in racine.iterdir():
|
|
if sous_dossier.is_mount() or (sous_dossier.is_dir() and any(sous_dossier.iterdir())):
|
|
usb_trouvees.append(str(sous_dossier))
|
|
return usb_trouvees
|
|
|
|
|
|
def compteur_restant() -> dict:
|
|
"""Retourne l'etat du compteur."""
|
|
config = charger_config()
|
|
compteur = config.get("compteur", {})
|
|
limite = compteur.get("limite", 400)
|
|
prises = compteur.get("photos_prises", 0)
|
|
return {
|
|
"actif": compteur.get("actif", False),
|
|
"limite": limite,
|
|
"photos_prises": prises,
|
|
"restantes": max(0, limite - prises),
|
|
}
|
|
|
|
|
|
def reset_compteur():
|
|
"""Remet le compteur a zero."""
|
|
mettre_a_jour_config({"compteur": {"photos_prises": 0}})
|