Flash Canon configurable, choix de cadre a l'impression, fixes capture/reconnexion DSLR
- Flash integre Canon activable/desactivable depuis l'admin (configurer_flash) - Popup de choix de cadre juste avant impression, layout sans defilement (flex nowrap + aspect-ratio) - Capture : ignore camera_erreur pendant une capture en cours (laisse l'API trancher) - surveiller_dslr : grace period preview augmentee a 2s, reapplique la config camera apres reconnexion - start.sh simplifie (Chromium gere par kiosk-session.sh), remote debugging port 9222 active
This commit is contained in:
@@ -212,6 +212,33 @@ class Camera:
|
|||||||
else:
|
else:
|
||||||
return None # Pas de simulation ni webcam en dehors du mode dedie
|
return None # Pas de simulation ni webcam en dehors du mode dedie
|
||||||
|
|
||||||
|
def configurer_flash(self, actif: bool):
|
||||||
|
"""Active ou désactive le flash intégré Canon.
|
||||||
|
Essaie plusieurs noms de widgets selon le modèle."""
|
||||||
|
if not self.camera or self.mode != "gphoto2":
|
||||||
|
return
|
||||||
|
# strobofiring : "0"=inhibé(off), "2"=auto — Canon EOS 60D (widget radio, valeurs string)
|
||||||
|
# flashmode : "On"/"Off" — autres Canon
|
||||||
|
candidats = [
|
||||||
|
("strobofiring", "2", "0"), # (widget, valeur_on, valeur_off)
|
||||||
|
("flashmode", "On", "Off"),
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
with self._gp_lock:
|
||||||
|
cfg = self.camera.get_config()
|
||||||
|
for nom, val_on, val_off in candidats:
|
||||||
|
try:
|
||||||
|
widget = cfg.get_child_by_name(nom)
|
||||||
|
widget.set_value(val_on if actif else val_off)
|
||||||
|
self.camera.set_config(cfg)
|
||||||
|
log.info(f"Flash intégré ({nom}) : {'On' if actif else 'Off'}")
|
||||||
|
return
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
log.debug("Aucun widget flash trouvé sur ce modèle Canon")
|
||||||
|
except Exception as e:
|
||||||
|
log.debug(f"configurer_flash : {e}")
|
||||||
|
|
||||||
def _activer_viewfinder_init(self):
|
def _activer_viewfinder_init(self):
|
||||||
"""Active le LiveView pendant l'init (pas de lock, appelé avant que le thread démarre)."""
|
"""Active le LiveView pendant l'init (pas de lock, appelé avant que le thread démarre)."""
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ _loop_principal: asyncio.AbstractEventLoop | None = None # loop asyncio princip
|
|||||||
|
|
||||||
|
|
||||||
_preview_none_count = 0
|
_preview_none_count = 0
|
||||||
_PREVIEW_GRACE_FRAMES = 15 # ~500ms avant de declarer une erreur
|
_PREVIEW_GRACE_FRAMES = 40 # 40 x 50ms = 2s avant de declarer une erreur (Canon LiveView peut être lent à démarrer)
|
||||||
|
|
||||||
|
|
||||||
def _thread_preview():
|
def _thread_preview():
|
||||||
@@ -88,6 +88,7 @@ async def _pusher_preview():
|
|||||||
# Grace period : attendre plusieurs frames avant de signaler l'erreur
|
# Grace period : attendre plusieurs frames avant de signaler l'erreur
|
||||||
# Ne pas signaler pendant une capture (race condition HTTP/WS)
|
# Ne pas signaler pendant une capture (race condition HTTP/WS)
|
||||||
if camera.mode == "gphoto2" and _preview_none_count > _PREVIEW_GRACE_FRAMES and not capture_en_cours:
|
if camera.mode == "gphoto2" and _preview_none_count > _PREVIEW_GRACE_FRAMES and not capture_en_cours:
|
||||||
|
log.warning(f"camera_erreur envoyé : preview None depuis {_preview_none_count * 50}ms")
|
||||||
await diffuser_ws({"type": "camera_erreur", "message": "DSLR ne repond pas au preview"})
|
await diffuser_ws({"type": "camera_erreur", "message": "DSLR ne repond pas au preview"})
|
||||||
_preview_actif = False
|
_preview_actif = False
|
||||||
_preview_none_count = 0
|
_preview_none_count = 0
|
||||||
@@ -134,8 +135,8 @@ async def surveiller_dslr():
|
|||||||
_echecs_connexion = 0
|
_echecs_connexion = 0
|
||||||
else:
|
else:
|
||||||
_dslr_erreurs += 1
|
_dslr_erreurs += 1
|
||||||
if _dslr_erreurs >= 3: # 3 x 5s = 15s sans preview valide
|
if _dslr_erreurs >= 3 and not _preview_actif: # Ne pas couper pendant le countdown
|
||||||
log.warning("DSLR ne repond plus (preview KO depuis 15s), reconnexion forcee...")
|
log.warning(f"camera_erreur : DSLR preview KO depuis {_dslr_erreurs * 5}s, reconnexion forcee...")
|
||||||
_dslr_erreurs = 0
|
_dslr_erreurs = 0
|
||||||
camera.deconnecter()
|
camera.deconnecter()
|
||||||
await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"})
|
await diffuser_ws({"type": "camera_erreur", "message": "Appareil photo deconnecte"})
|
||||||
@@ -144,6 +145,7 @@ async def surveiller_dslr():
|
|||||||
if camera.connectee:
|
if camera.connectee:
|
||||||
log.info("DSLR reconnecte avec succes")
|
log.info("DSLR reconnecte avec succes")
|
||||||
_echecs_connexion = 0
|
_echecs_connexion = 0
|
||||||
|
_appliquer_config_camera()
|
||||||
await diffuser_ws({"type": "camera_ok"})
|
await diffuser_ws({"type": "camera_ok"})
|
||||||
else:
|
else:
|
||||||
log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s")
|
log.warning("Echec reconnexion DSLR — nouvelle tentative dans 5s")
|
||||||
@@ -156,6 +158,7 @@ async def surveiller_dslr():
|
|||||||
if camera.connectee:
|
if camera.connectee:
|
||||||
log.info("DSLR connecte avec succes")
|
log.info("DSLR connecte avec succes")
|
||||||
_echecs_connexion = 0
|
_echecs_connexion = 0
|
||||||
|
_appliquer_config_camera()
|
||||||
await diffuser_ws({"type": "camera_ok"})
|
await diffuser_ws({"type": "camera_ok"})
|
||||||
else:
|
else:
|
||||||
_echecs_connexion += 1
|
_echecs_connexion += 1
|
||||||
@@ -164,6 +167,13 @@ async def surveiller_dslr():
|
|||||||
log.debug(f"surveiller_dslr: {e}")
|
log.debug(f"surveiller_dslr: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def _appliquer_config_camera():
|
||||||
|
"""Applique les paramètres caméra persistants (flash, etc.) après connexion."""
|
||||||
|
cfg = charger_config()
|
||||||
|
flash_integre = cfg.get("camera", {}).get("flash_integre", True)
|
||||||
|
camera.configurer_flash(flash_integre)
|
||||||
|
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
"""Demarrage et arret de l'application."""
|
"""Demarrage et arret de l'application."""
|
||||||
@@ -171,6 +181,7 @@ async def lifespan(app: FastAPI):
|
|||||||
log.info("Demarrage du photobooth")
|
log.info("Demarrage du photobooth")
|
||||||
_loop_principal = asyncio.get_event_loop()
|
_loop_principal = asyncio.get_event_loop()
|
||||||
camera.connecter()
|
camera.connecter()
|
||||||
|
_appliquer_config_camera()
|
||||||
# Thread de capture preview (bloquant, tourne en parallele)
|
# Thread de capture preview (bloquant, tourne en parallele)
|
||||||
t = threading.Thread(target=_thread_preview, daemon=True)
|
t = threading.Thread(target=_thread_preview, daemon=True)
|
||||||
t.start()
|
t.start()
|
||||||
@@ -276,6 +287,9 @@ async def api_config():
|
|||||||
async def api_config_update(modifications: dict):
|
async def api_config_update(modifications: dict):
|
||||||
config = mettre_a_jour_config(modifications)
|
config = mettre_a_jour_config(modifications)
|
||||||
await diffuser_ws({"type": "config_maj", "config": config})
|
await diffuser_ws({"type": "config_maj", "config": config})
|
||||||
|
# Appliquer immédiatement les paramètres caméra si modifiés
|
||||||
|
if "camera" in modifications and camera.connectee:
|
||||||
|
_appliquer_config_camera()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@@ -430,6 +444,13 @@ async def api_systeme_info():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/log-js")
|
||||||
|
async def api_log_js(request: Request):
|
||||||
|
data = await request.json()
|
||||||
|
log.warning(f"[JS] {data.get('msg', '?')}")
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/camera/statut")
|
@app.get("/api/camera/statut")
|
||||||
async def api_camera_statut():
|
async def api_camera_statut():
|
||||||
return {
|
return {
|
||||||
@@ -476,6 +497,8 @@ async def api_camera_reconnecter(body: dict = {}):
|
|||||||
camera.deconnecter()
|
camera.deconnecter()
|
||||||
source = body.get("source")
|
source = body.get("source")
|
||||||
ok = camera.connecter(source=source)
|
ok = camera.connecter(source=source)
|
||||||
|
if ok:
|
||||||
|
_appliquer_config_camera()
|
||||||
return {"connectee": ok, "mode": camera.mode}
|
return {"connectee": ok, "mode": camera.mode}
|
||||||
|
|
||||||
|
|
||||||
@@ -1073,8 +1096,10 @@ async def traiter_message_ws(msg: dict, ws: WebSocket):
|
|||||||
elif type_msg == "preview_start":
|
elif type_msg == "preview_start":
|
||||||
global _preview_actif
|
global _preview_actif
|
||||||
_preview_actif = True
|
_preview_actif = True
|
||||||
|
log.info("WS: preview_start recu")
|
||||||
elif type_msg == "preview_stop":
|
elif type_msg == "preview_stop":
|
||||||
_preview_actif = False
|
_preview_actif = False
|
||||||
|
log.info("WS: preview_stop recu")
|
||||||
|
|
||||||
|
|
||||||
async def diffuser_ws(message: dict):
|
async def diffuser_ws(message: dict):
|
||||||
|
|||||||
@@ -1342,6 +1342,64 @@ h3 {
|
|||||||
color: var(--texte-secondaire);
|
color: var(--texte-secondaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cadre-choix-item.cadre-selectionne {
|
||||||
|
border-color: var(--primaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Popup sélection cadre au moment de l'impression */
|
||||||
|
.popup-box-cadre {
|
||||||
|
max-width: 92vw;
|
||||||
|
width: 680px;
|
||||||
|
max-height: 90vh;
|
||||||
|
padding: 1.2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
min-width: 0;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 0.6rem;
|
||||||
|
padding: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup .cadre-choix-item {
|
||||||
|
flex: 0 1 78px;
|
||||||
|
min-width: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup .cadre-choix-item .cadre-thumb-wrap {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup .cadre-choix-item .cadre-thumb-wrap img {
|
||||||
|
width: 250%;
|
||||||
|
height: 250%;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup .cadre-choix-aucun .cadre-choix-vide {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
height: auto;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grille-cadres-popup .cadre-choix-item span {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.cadre-choix-aucun .cadre-choix-vide {
|
.cadre-choix-aucun .cadre-choix-vide {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
height: 210px;
|
height: 210px;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
<meta name="google" content="notranslate">
|
<meta name="google" content="notranslate">
|
||||||
<meta http-equiv="Content-Language" content="fr">
|
<meta http-equiv="Content-Language" content="fr">
|
||||||
<link rel="stylesheet" href="/css/style.css">
|
<link rel="stylesheet" href="/css/style.css?v=2">
|
||||||
<link rel="stylesheet" href="/css/themes.css">
|
<link rel="stylesheet" href="/css/themes.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -144,7 +144,6 @@
|
|||||||
<span>QR Code</span>
|
<span>QR Code</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Choix exemplaires impression -->
|
|
||||||
<div id="form-impression" class="form-impression cache">
|
<div id="form-impression" class="form-impression cache">
|
||||||
<div class="exemplaires-choix">
|
<div class="exemplaires-choix">
|
||||||
<button class="btn-exemplaire" onclick="changerExemplaires(-1)">-</button>
|
<button class="btn-exemplaire" onclick="changerExemplaires(-1)">-</button>
|
||||||
@@ -379,6 +378,9 @@
|
|||||||
<label>Compte a rebours (secondes)</label>
|
<label>Compte a rebours (secondes)</label>
|
||||||
<input type="number" id="admin-car" min="1" max="10" value="3">
|
<input type="number" id="admin-car" min="1" max="10" value="3">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<label class="toggle"><input type="checkbox" id="tog-flash-integre" checked><span class="toggle-slider"></span> Flash intégré Canon (décocher = flash Off)</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Imprimante</h3>
|
<h3>Imprimante</h3>
|
||||||
<div class="champ">
|
<div class="champ">
|
||||||
@@ -672,6 +674,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Popup sélection cadre impression -->
|
||||||
|
<div id="popup-cadre-impression" class="popup-overlay cache">
|
||||||
|
<div class="popup-box popup-box-cadre">
|
||||||
|
<h3>Cadre d'impression</h3>
|
||||||
|
<div id="grille-cadres-impression" class="grille-cadres-choix grille-cadres-popup"></div>
|
||||||
|
<div class="popup-actions">
|
||||||
|
<button class="btn-action" onclick="confirmerCadreImpression()">Confirmer</button>
|
||||||
|
<button class="btn-secondaire" onclick="annulerCadreImpression()">Annuler</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Popup PIN photostation -->
|
<!-- Popup PIN photostation -->
|
||||||
<div id="popup-pin-photostation" class="popup-overlay cache">
|
<div id="popup-pin-photostation" class="popup-overlay cache">
|
||||||
<div class="popup-box popup-box-pave">
|
<div class="popup-box popup-box-pave">
|
||||||
@@ -722,12 +736,12 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/js/websocket.js"></script>
|
<script src="/js/websocket.js?v=3"></script>
|
||||||
<script src="/js/app.js"></script>
|
<script src="/js/app.js?v=4"></script>
|
||||||
<script src="/js/camera.js"></script>
|
<script src="/js/camera.js?v=6"></script>
|
||||||
<script src="/js/effects.js"></script>
|
<script src="/js/effects.js?v=3"></script>
|
||||||
<script src="/js/gallery.js"></script>
|
<script src="/js/gallery.js?v=3"></script>
|
||||||
<script src="/js/share.js"></script>
|
<script src="/js/share.js?v=5"></script>
|
||||||
<script src="/js/admin.js"></script>
|
<script src="/js/admin.js?v=4"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ async function chargerMateriel() {
|
|||||||
if (cam.appareil) selectApp.value = cam.appareil;
|
if (cam.appareil) selectApp.value = cam.appareil;
|
||||||
|
|
||||||
setValue('admin-car', cam.compte_a_rebours || 3);
|
setValue('admin-car', cam.compte_a_rebours || 3);
|
||||||
|
document.getElementById('tog-flash-integre').checked = cam.flash_integre !== false;
|
||||||
|
|
||||||
// Imprimantes
|
// Imprimantes
|
||||||
await rafraichirImprimantes();
|
await rafraichirImprimantes();
|
||||||
@@ -135,10 +136,12 @@ async function rafraichirStatutImprimante() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sauvegarderMateriel() {
|
async function sauvegarderMateriel() {
|
||||||
|
const flashIntegre = document.getElementById('tog-flash-integre').checked;
|
||||||
await apiPost('/api/config', {
|
await apiPost('/api/config', {
|
||||||
camera: {
|
camera: {
|
||||||
appareil: getValue('admin-appareil'),
|
appareil: getValue('admin-appareil'),
|
||||||
compte_a_rebours: parseInt(getValue('admin-car')) || 3,
|
compte_a_rebours: parseInt(getValue('admin-car')) || 3,
|
||||||
|
flash_integre: flashIntegre,
|
||||||
},
|
},
|
||||||
impression: {
|
impression: {
|
||||||
imprimante: getValue('admin-imprimante'),
|
imprimante: getValue('admin-imprimante'),
|
||||||
|
|||||||
@@ -247,11 +247,10 @@ let cadreChoisi = null; // cadre sélectionné par l'utilisateur avant capture
|
|||||||
|
|
||||||
function setupModes() {
|
function setupModes() {
|
||||||
document.querySelectorAll('#ecran-mode .btn-mode').forEach(btn => {
|
document.querySelectorAll('#ecran-mode .btn-mode').forEach(btn => {
|
||||||
btn.addEventListener('click', async () => {
|
btn.addEventListener('click', () => {
|
||||||
modeActuel = btn.dataset.mode;
|
modeActuel = btn.dataset.mode;
|
||||||
cadreChoisi = null;
|
cadreChoisi = null;
|
||||||
const hasCadres = await chargerCadresChoix();
|
allerA('capture');
|
||||||
allerA(hasCadres ? 'cadre-choix' : 'capture');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
let previewActif = false;
|
let previewActif = false;
|
||||||
let previewTimeoutId = null;
|
let previewTimeoutId = null;
|
||||||
|
let previewInterval = null;
|
||||||
const PREVIEW_TIMEOUT_MS = 3000;
|
const PREVIEW_TIMEOUT_MS = 3000;
|
||||||
let captureAbortee = false;
|
let captureAbortee = false;
|
||||||
|
let captureEnCours = false; // true pendant tout le flux lancerCapture()
|
||||||
|
|
||||||
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||||
|
|
||||||
@@ -73,14 +75,17 @@ wsOnMessage('preview', (msg) => {
|
|||||||
resetPreviewTimeout();
|
resetPreviewTimeout();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Erreur camera : abort capture + retour accueil si on est en train de capturer
|
// Erreur camera pendant preview idle : retour accueil
|
||||||
|
// Si on est en cours de capture (compte à rebours ou attente API), on ignore :
|
||||||
|
// l'API /api/capturer retournera elle-même une erreur si le DSLR est vraiment mort.
|
||||||
wsOnMessage('camera_erreur', () => {
|
wsOnMessage('camera_erreur', () => {
|
||||||
captureAbortee = true;
|
|
||||||
arreterPreview();
|
arreterPreview();
|
||||||
|
if (!captureEnCours) {
|
||||||
|
captureAbortee = true;
|
||||||
if (ecranActuel === 'capture') {
|
if (ecranActuel === 'capture') {
|
||||||
// Retour accueil automatique en 4s (afficherErreurCapture le fait)
|
|
||||||
afficherErreurCapture('Appareil photo déconnecté', 'Reconnexion en cours...');
|
afficherErreurCapture('Appareil photo déconnecté', 'Reconnexion en cours...');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
wsOnMessage('camera_ok', () => {
|
wsOnMessage('camera_ok', () => {
|
||||||
@@ -91,6 +96,7 @@ wsOnMessage('camera_ok', () => {
|
|||||||
// --- Capture ---
|
// --- Capture ---
|
||||||
|
|
||||||
async function lancerCapture() {
|
async function lancerCapture() {
|
||||||
|
captureEnCours = true;
|
||||||
captureAbortee = false;
|
captureAbortee = false;
|
||||||
photosSession = [];
|
photosSession = [];
|
||||||
photoImpression = null;
|
photoImpression = null;
|
||||||
@@ -105,10 +111,12 @@ async function lancerCapture() {
|
|||||||
try {
|
try {
|
||||||
const statut = await apiGet('/api/camera/statut');
|
const statut = await apiGet('/api/camera/statut');
|
||||||
if (!statut.connectee || statut.mode === 'erreur') {
|
if (!statut.connectee || statut.mode === 'erreur') {
|
||||||
|
captureEnCours = false;
|
||||||
afficherErreurCapture('Appareil photo non disponible', 'Verifiez la connexion USB du DSLR');
|
afficherErreurCapture('Appareil photo non disponible', 'Verifiez la connexion USB du DSLR');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
captureEnCours = false;
|
||||||
afficherErreurCapture('Impossible de joindre le serveur', 'Verifiez que le photobooth est bien demarre');
|
afficherErreurCapture('Impossible de joindre le serveur', 'Verifiez que le photobooth est bien demarre');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -117,20 +125,14 @@ async function lancerCapture() {
|
|||||||
const compteurEl = document.getElementById('capture-compteur');
|
const compteurEl = document.getElementById('capture-compteur');
|
||||||
|
|
||||||
for (let i = 0; i < nbPhotos; i++) {
|
for (let i = 0; i < nbPhotos; i++) {
|
||||||
if (captureAbortee) return;
|
|
||||||
|
|
||||||
if (nbPhotos > 1) {
|
if (nbPhotos > 1) {
|
||||||
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
lancerPreview(); // Miroir live pendant le compte à rebours
|
lancerPreview(); // Miroir live pendant le compte à rebours
|
||||||
await compteARebours();
|
await compteARebours();
|
||||||
arreterPreview(); // Libère le bus USB gphoto2 pour la capture
|
arreterPreview();
|
||||||
|
await pause(150);
|
||||||
if (captureAbortee) return;
|
|
||||||
|
|
||||||
afficherFlash();
|
|
||||||
document.getElementById('capture-en-cours').classList.remove('cache');
|
|
||||||
|
|
||||||
const promesseCapture = apiPost('/api/capturer').catch(() => null);
|
const promesseCapture = apiPost('/api/capturer').catch(() => null);
|
||||||
let resultat = await promesseCapture;
|
let resultat = await promesseCapture;
|
||||||
@@ -140,16 +142,19 @@ async function lancerCapture() {
|
|||||||
if (!resultat) {
|
if (!resultat) {
|
||||||
erreurReseau = true;
|
erreurReseau = true;
|
||||||
} else if (resultat.erreur === 'Limite de photos atteinte') {
|
} else if (resultat.erreur === 'Limite de photos atteinte') {
|
||||||
|
captureEnCours = false;
|
||||||
afficherErreurCapture('Limite de photos atteinte', 'La session est terminee');
|
afficherErreurCapture('Limite de photos atteinte', 'La session est terminee');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (erreurReseau) {
|
if (erreurReseau) {
|
||||||
|
captureEnCours = false;
|
||||||
afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth');
|
afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resultat || resultat.erreur) {
|
if (!resultat || resultat.erreur) {
|
||||||
|
captureEnCours = false;
|
||||||
const msg = resultat?.erreur || 'Erreur inconnue';
|
const msg = resultat?.erreur || 'Erreur inconnue';
|
||||||
afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg);
|
afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg);
|
||||||
return;
|
return;
|
||||||
@@ -160,6 +165,7 @@ async function lancerCapture() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
captureEnCours = false;
|
||||||
await traiterCapture();
|
await traiterCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,66 @@
|
|||||||
let nbExemplaires = 1;
|
let nbExemplaires = 1;
|
||||||
let copiesMax = 5;
|
let copiesMax = 5;
|
||||||
|
|
||||||
function ouvrirImpression() {
|
async function ouvrirImpression() {
|
||||||
copiesMax = config.impression?.copies_max || 5;
|
copiesMax = config.impression?.copies_max || 5;
|
||||||
nbExemplaires = 1;
|
nbExemplaires = 1;
|
||||||
document.getElementById('nb-exemplaires').textContent = nbExemplaires;
|
document.getElementById('nb-exemplaires').textContent = nbExemplaires;
|
||||||
|
cadreChoisi = null;
|
||||||
|
|
||||||
|
const hasCadres = await chargerCadresChoixImpression();
|
||||||
|
if (hasCadres) {
|
||||||
|
document.getElementById('popup-cadre-impression').classList.remove('cache');
|
||||||
|
} else {
|
||||||
document.getElementById('form-impression').classList.remove('cache');
|
document.getElementById('form-impression').classList.remove('cache');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function fermerImpression() {
|
function fermerImpression() {
|
||||||
document.getElementById('form-impression').classList.add('cache');
|
document.getElementById('form-impression').classList.add('cache');
|
||||||
|
cadreChoisi = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function confirmerCadreImpression() {
|
||||||
|
document.getElementById('popup-cadre-impression').classList.add('cache');
|
||||||
|
document.getElementById('form-impression').classList.remove('cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
function annulerCadreImpression() {
|
||||||
|
document.getElementById('popup-cadre-impression').classList.add('cache');
|
||||||
|
cadreChoisi = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function chargerCadresChoixImpression() {
|
||||||
|
const fmt = (config.impression?.format || '15x20').replace('-2up', '');
|
||||||
|
const data = await apiGet(`/api/cadres-impression/${fmt}`).catch(() => null);
|
||||||
|
if (!data || data.disponibles.length === 0) return false;
|
||||||
|
|
||||||
|
const grille = document.getElementById('grille-cadres-impression');
|
||||||
|
grille.innerHTML = '';
|
||||||
|
|
||||||
|
const divAucun = document.createElement('div');
|
||||||
|
divAucun.className = 'cadre-choix-item cadre-choix-aucun cadre-selectionne';
|
||||||
|
divAucun.innerHTML = '<div class="cadre-choix-vide"></div><span>Sans cadre</span>';
|
||||||
|
divAucun.onclick = () => {
|
||||||
|
cadreChoisi = null;
|
||||||
|
grille.querySelectorAll('.cadre-choix-item').forEach(el => el.classList.remove('cadre-selectionne'));
|
||||||
|
divAucun.classList.add('cadre-selectionne');
|
||||||
|
};
|
||||||
|
grille.appendChild(divAucun);
|
||||||
|
|
||||||
|
for (const nom of data.disponibles) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'cadre-choix-item';
|
||||||
|
div.innerHTML = `<div class="cadre-thumb-wrap"><img src="/assets/cadres/${fmt}/${nom}" alt="${nom}"></div><span>${nom.replace('.png','')}</span>`;
|
||||||
|
div.onclick = () => {
|
||||||
|
cadreChoisi = nom;
|
||||||
|
grille.querySelectorAll('.cadre-choix-item').forEach(el => el.classList.remove('cadre-selectionne'));
|
||||||
|
div.classList.add('cadre-selectionne');
|
||||||
|
};
|
||||||
|
grille.appendChild(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function changerExemplaires(delta) {
|
function changerExemplaires(delta) {
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ while true; do
|
|||||||
--force-device-scale-factor=3 \
|
--force-device-scale-factor=3 \
|
||||||
--password-store=basic \
|
--password-store=basic \
|
||||||
--lang=fr \
|
--lang=fr \
|
||||||
|
--remote-debugging-port=9222 \
|
||||||
http://localhost
|
http://localhost
|
||||||
echo "[kiosk] Chromium terminé, redémarrage dans 2s..."
|
echo "[kiosk] Chromium terminé, redémarrage dans 2s..."
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Demarrage du photobooth (backend + chromium kiosk)
|
# Demarrage du backend photobooth (Chromium est gere par la session kiosk)
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
@@ -8,32 +8,5 @@ cd "$DIR"
|
|||||||
# Activer le venv
|
# Activer le venv
|
||||||
source .venv/bin/activate
|
source .venv/bin/activate
|
||||||
|
|
||||||
# Demarrer le backend en arriere-plan
|
echo "Demarrage du backend photobooth..."
|
||||||
echo "Demarrage du backend..."
|
exec python -m backend.main
|
||||||
python -m backend.main &
|
|
||||||
BACKEND_PID=$!
|
|
||||||
|
|
||||||
# Attendre que le backend soit pret
|
|
||||||
sleep 3
|
|
||||||
|
|
||||||
# Cacher le curseur souris
|
|
||||||
unclutter -idle 0.1 -root &
|
|
||||||
|
|
||||||
# Demarrer Chromium en mode kiosk
|
|
||||||
echo "Demarrage de Chromium kiosk..."
|
|
||||||
chromium-browser \
|
|
||||||
--kiosk \
|
|
||||||
--noerrdialogs \
|
|
||||||
--disable-infobars \
|
|
||||||
--disable-translate \
|
|
||||||
--disable-features=TranslateUI \
|
|
||||||
--disable-session-crashed-bubble \
|
|
||||||
--disable-component-update \
|
|
||||||
--check-for-update-interval=31536000 \
|
|
||||||
--autoplay-policy=no-user-gesture-required \
|
|
||||||
--start-fullscreen \
|
|
||||||
--incognito \
|
|
||||||
http://localhost:8080
|
|
||||||
|
|
||||||
# Si Chromium se ferme, arreter le backend
|
|
||||||
kill $BACKEND_PID 2>/dev/null
|
|
||||||
|
|||||||
Reference in New Issue
Block a user