Admin backoffice : rubriques General/Evenement + videos situation + surprise + rembobinage ruban
- Reorganisation menu admin en 2 rubriques (General / Evenement) - Destinations deplace dans Evenement - Nouveau panneau Videos : upload par situation (montage, depannage, bourrage, rechargement) - Nouveau panneau Surprise : photo/video affichee ~1s avant capture pour provoquer sourire - Toggle rembobinage ruban Mitsubishi (StpiDecklist) pour economiser le ruban sur petits formats - Historique email cloisonne par evenement - Memoire projet consolidee dans memoire.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -195,28 +195,47 @@ async def tache_spool_demarrage():
|
||||
FICHIER_EMAILS = RACINE / "data" / "emails_history.json"
|
||||
|
||||
|
||||
def _event_id_actif() -> str:
|
||||
return charger_config().get("evenement", {}).get("event_id") or "_sans_evenement"
|
||||
|
||||
|
||||
def _charger_toutes_historiques() -> dict:
|
||||
if not FICHIER_EMAILS.exists():
|
||||
return {}
|
||||
try:
|
||||
with open(FICHIER_EMAILS, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
# Compatibilite avec l'ancien format (liste globale non cloisonnee)
|
||||
if isinstance(data, list):
|
||||
return {"_sans_evenement": data}
|
||||
return data
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {}
|
||||
|
||||
|
||||
def _sauvegarder_email_historique(email: str):
|
||||
historique = charger_emails_historique()
|
||||
toutes = _charger_toutes_historiques()
|
||||
event_id = _event_id_actif()
|
||||
historique = toutes.setdefault(event_id, [])
|
||||
email_lower = email.lower().strip()
|
||||
if email_lower not in historique:
|
||||
historique.append(email_lower)
|
||||
try:
|
||||
with open(FICHIER_EMAILS, "w", encoding="utf-8") as f:
|
||||
json.dump(historique, f, ensure_ascii=False)
|
||||
json.dump(toutes, f, ensure_ascii=False)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def charger_emails_historique() -> list:
|
||||
if not FICHIER_EMAILS.exists():
|
||||
return []
|
||||
try:
|
||||
with open(FICHIER_EMAILS, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return []
|
||||
return _charger_toutes_historiques().get(_event_id_actif(), [])
|
||||
|
||||
|
||||
def effacer_emails_historique():
|
||||
if FICHIER_EMAILS.exists():
|
||||
FICHIER_EMAILS.unlink()
|
||||
toutes = _charger_toutes_historiques()
|
||||
toutes.pop(_event_id_actif(), None)
|
||||
try:
|
||||
with open(FICHIER_EMAILS, "w", encoding="utf-8") as f:
|
||||
json.dump(toutes, f, ensure_ascii=False)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
@@ -1072,19 +1072,28 @@ async def api_obtenir_evenement(event_id: str):
|
||||
return event
|
||||
|
||||
|
||||
def _event_id_actif() -> str | None:
|
||||
return charger_config().get("evenement", {}).get("event_id")
|
||||
|
||||
|
||||
@app.put("/api/evenements/{event_id}")
|
||||
async def api_modifier_evenement(event_id: str, donnees: dict):
|
||||
event = modifier_evenement(event_id, donnees)
|
||||
if not event:
|
||||
return JSONResponse({"erreur": "Evenement introuvable"}, status_code=404)
|
||||
if event_id == _event_id_actif():
|
||||
await diffuser_ws({"type": "config_maj", "config": charger_config()})
|
||||
return event
|
||||
|
||||
|
||||
@app.delete("/api/evenements/{event_id}")
|
||||
async def api_supprimer_evenement(event_id: str):
|
||||
etait_actif = event_id == _event_id_actif()
|
||||
ok = supprimer_evenement(event_id)
|
||||
if not ok:
|
||||
return JSONResponse({"erreur": "Evenement introuvable"}, status_code=404)
|
||||
if etait_actif:
|
||||
await diffuser_ws({"type": "config_maj", "config": charger_config()})
|
||||
return {"succes": True}
|
||||
|
||||
|
||||
@@ -1093,6 +1102,7 @@ async def api_activer_evenement(event_id: str):
|
||||
event = activer_evenement(event_id)
|
||||
if not event:
|
||||
return JSONResponse({"erreur": "Evenement introuvable"}, status_code=404)
|
||||
await diffuser_ws({"type": "config_maj", "config": charger_config()})
|
||||
return event
|
||||
|
||||
|
||||
@@ -1113,6 +1123,8 @@ async def api_set_cadre_event(event_id: str, format_papier: str, donnees: dict):
|
||||
mode = donnees.get("mode", "aucun")
|
||||
cadre = donnees.get("cadre")
|
||||
set_cadre_event(event_id, format_papier, mode, cadre)
|
||||
if event_id == _event_id_actif():
|
||||
await diffuser_ws({"type": "config_maj", "config": charger_config()})
|
||||
return {"succes": True}
|
||||
|
||||
|
||||
@@ -1384,6 +1396,87 @@ async def _lancer_photostation():
|
||||
FLAG_PHOTOSTATION.unlink(missing_ok=True)
|
||||
|
||||
|
||||
# --- API Videos de situation ---
|
||||
|
||||
DOSSIER_VIDEOS = RACINE / "data" / "videos"
|
||||
DOSSIER_VIDEOS.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@app.get("/api/videos")
|
||||
async def api_videos():
|
||||
videos = {}
|
||||
for f in DOSSIER_VIDEOS.iterdir():
|
||||
if f.is_file() and f.suffix.lower() in ('.mp4', '.webm', '.mov'):
|
||||
videos[f.stem] = f.name
|
||||
return {"videos": videos}
|
||||
|
||||
|
||||
@app.post("/api/videos/upload")
|
||||
async def api_upload_video(video: UploadFile = File(...), situation: str = ""):
|
||||
if not situation:
|
||||
return JSONResponse({"erreur": "situation requise"}, 400)
|
||||
ext = Path(video.filename).suffix.lower() or '.mp4'
|
||||
dest = DOSSIER_VIDEOS / f"{situation}{ext}"
|
||||
for old in DOSSIER_VIDEOS.glob(f"{situation}.*"):
|
||||
old.unlink()
|
||||
with open(dest, "wb") as f:
|
||||
while chunk := await video.read(1024 * 1024):
|
||||
f.write(chunk)
|
||||
return {"ok": True, "fichier": dest.name}
|
||||
|
||||
|
||||
@app.delete("/api/videos/{situation}")
|
||||
async def api_delete_video(situation: str):
|
||||
for f in DOSSIER_VIDEOS.glob(f"{situation}.*"):
|
||||
f.unlink()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/api/videos/{situation}/stream")
|
||||
async def api_stream_video(situation: str):
|
||||
for f in DOSSIER_VIDEOS.glob(f"{situation}.*"):
|
||||
media = "video/mp4" if f.suffix == ".mp4" else "video/webm"
|
||||
return FileResponse(f, media_type=media)
|
||||
return JSONResponse({"erreur": "video introuvable"}, 404)
|
||||
|
||||
|
||||
# --- API Surprise ---
|
||||
|
||||
DOSSIER_SURPRISE = RACINE / "data" / "surprise"
|
||||
DOSSIER_SURPRISE.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@app.post("/api/surprise/upload")
|
||||
async def api_upload_surprise(media: UploadFile = File(...)):
|
||||
for old in DOSSIER_SURPRISE.iterdir():
|
||||
old.unlink()
|
||||
ext = Path(media.filename).suffix.lower() or '.jpg'
|
||||
dest = DOSSIER_SURPRISE / f"surprise{ext}"
|
||||
with open(dest, "wb") as f:
|
||||
while chunk := await media.read(1024 * 1024):
|
||||
f.write(chunk)
|
||||
fichier_type = "video" if ext in ('.mp4', '.webm', '.mov') else "photo"
|
||||
mettre_a_jour_config({"surprise": {"fichier": dest.name, "type": fichier_type}})
|
||||
return {"ok": True, "fichier": dest.name, "type": fichier_type}
|
||||
|
||||
|
||||
@app.delete("/api/surprise/media")
|
||||
async def api_delete_surprise():
|
||||
for f in DOSSIER_SURPRISE.iterdir():
|
||||
f.unlink()
|
||||
mettre_a_jour_config({"surprise": {"fichier": None}})
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/api/surprise/media")
|
||||
async def api_get_surprise():
|
||||
for f in DOSSIER_SURPRISE.iterdir():
|
||||
if f.is_file():
|
||||
ext = f.suffix.lower()
|
||||
if ext in ('.mp4', '.webm', '.mov'):
|
||||
return FileResponse(f, media_type="video/mp4")
|
||||
return FileResponse(f, media_type="image/jpeg")
|
||||
return JSONResponse({"erreur": "aucun media"}, 404)
|
||||
|
||||
|
||||
# --- API Systeme ---
|
||||
|
||||
@app.post("/api/systeme/redemarrer")
|
||||
|
||||
@@ -299,6 +299,8 @@ def imprimer(
|
||||
}
|
||||
time.sleep(1)
|
||||
|
||||
rembobinage = conf_imp.get("rembobinage_ruban", False)
|
||||
|
||||
try:
|
||||
for tentative in range(1, 4):
|
||||
cmd = [
|
||||
@@ -307,8 +309,10 @@ def imprimer(
|
||||
"-n", str(copies),
|
||||
"-o", f"PageSize={page_size}",
|
||||
"-o", "StpiShrinkOutput=Crop",
|
||||
str(chemin_print),
|
||||
]
|
||||
if rembobinage:
|
||||
cmd.extend(["-o", "StpiDecklist=true"])
|
||||
cmd.append(str(chemin_print))
|
||||
|
||||
code, out, err = _run(cmd, timeout=30)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user