Backoffice complet : materiel, compteur, destinations, cadres, animations
- Onglet Materiel : choix appareil photo + imprimante - Compteur avec limite configurable (399/400) affiche sur l'accueil - Destinations multiples : memoire interne, cle USB, FTP, site web, email - Choix sauvegarder toutes les photos ou seulement les imprimees - Gestion des cadres : activation/desactivation + import - 7 animations de compte a rebours : classique, explosion, rebond, rotation 3D, fondu, emoji fun, tremblement - Preview des animations dans le backoffice - Module destinations.py (copie USB, envoi FTP, compteur) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
backend/destinations.py
Normal file
114
backend/destinations.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import ftplib
|
||||
import logging
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
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)
|
||||
|
||||
# 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 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}})
|
||||
@@ -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
|
||||
|
||||
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur
|
||||
from backend.printer import lister_imprimantes, imprimer
|
||||
from backend.mailer import envoyer_photo
|
||||
from backend.qrcode_gen import generer_qr, qr_galerie
|
||||
@@ -73,10 +73,19 @@ async def api_config_update(modifications: dict):
|
||||
|
||||
@app.post("/api/capturer")
|
||||
async def api_capturer():
|
||||
# Verifier le compteur
|
||||
etat = compteur_restant()
|
||||
if etat["actif"] and etat["restantes"] <= 0:
|
||||
return JSONResponse({"erreur": "Limite de photos atteinte"}, status_code=403)
|
||||
|
||||
chemin = camera.capturer()
|
||||
if chemin is None:
|
||||
return JSONResponse({"erreur": "Echec capture"}, status_code=500)
|
||||
nom = chemin.name
|
||||
|
||||
# Distribuer vers les destinations
|
||||
distribuer_photo(chemin, imprimee=False)
|
||||
|
||||
await diffuser_ws({"type": "photo_capturee", "nom": nom})
|
||||
return {"nom": nom, "chemin": f"/data/photos/{nom}"}
|
||||
|
||||
@@ -206,6 +215,8 @@ async def api_imprimer(donnees: dict):
|
||||
if not chemin.exists():
|
||||
return JSONResponse({"erreur": "Photo introuvable"}, status_code=404)
|
||||
ok = imprimer(chemin)
|
||||
if ok:
|
||||
distribuer_photo(chemin, imprimee=True)
|
||||
return {"succes": ok}
|
||||
|
||||
|
||||
@@ -278,6 +289,60 @@ async def api_upload_fond(fichier: UploadFile = File(...)):
|
||||
return {"nom": fichier.filename}
|
||||
|
||||
|
||||
# --- API Compteur ---
|
||||
|
||||
@app.get("/api/compteur")
|
||||
async def api_compteur_etat():
|
||||
return compteur_restant()
|
||||
|
||||
|
||||
@app.post("/api/compteur/reset")
|
||||
async def api_compteur_reset():
|
||||
reset_compteur()
|
||||
return compteur_restant()
|
||||
|
||||
|
||||
# --- API Destinations ---
|
||||
|
||||
@app.get("/api/usb/detecter")
|
||||
async def api_detecter_usb():
|
||||
return detecter_usb()
|
||||
|
||||
|
||||
# --- API Cadres actifs ---
|
||||
|
||||
@app.get("/api/cadres")
|
||||
async def api_cadres():
|
||||
config = charger_config()
|
||||
tous = lister_overlays()
|
||||
actifs = config.get("cadres", {}).get("actifs", [])
|
||||
return {"tous": tous, "actifs": actifs}
|
||||
|
||||
|
||||
@app.post("/api/cadres")
|
||||
async def api_cadres_update(donnees: dict):
|
||||
actifs = donnees.get("actifs", [])
|
||||
mettre_a_jour_config({"cadres": {"actifs": actifs}})
|
||||
return {"actifs": actifs}
|
||||
|
||||
|
||||
# --- API Animations compte a rebours ---
|
||||
|
||||
ANIMATIONS_CAR = {
|
||||
"classique": "Classique (chiffres simples)",
|
||||
"explosion": "Explosion (chiffres qui eclatent)",
|
||||
"rebond": "Rebond (chiffres qui rebondissent)",
|
||||
"rotation": "Rotation 3D",
|
||||
"fondu": "Fondu enchaine",
|
||||
"emoji": "Emoji fun",
|
||||
"shake": "Tremblement",
|
||||
}
|
||||
|
||||
@app.get("/api/animations")
|
||||
async def api_animations():
|
||||
return ANIMATIONS_CAR
|
||||
|
||||
|
||||
# --- API Systeme ---
|
||||
|
||||
@app.post("/api/systeme/redemarrer")
|
||||
|
||||
Reference in New Issue
Block a user