Compare commits
3 Commits
c27b7152d6
...
cf41f66367
| Author | SHA1 | Date | |
|---|---|---|---|
| cf41f66367 | |||
| f9fd4c4f22 | |||
| ef6679dc97 |
@@ -64,6 +64,7 @@ class Camera:
|
|||||||
self.webcam = None
|
self.webcam = None
|
||||||
self.webcam_index = -1
|
self.webcam_index = -1
|
||||||
self._preview_webcam_fallback = None
|
self._preview_webcam_fallback = None
|
||||||
|
self.preview_dslr_ok = True # True si le dernier preview DSLR a reussi
|
||||||
|
|
||||||
def connecter(self, source=None) -> bool:
|
def connecter(self, source=None) -> bool:
|
||||||
"""Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc."""
|
"""Connecte la camera. source: 'gphoto2', 'webcam:0', 'webcam:2', etc."""
|
||||||
@@ -202,7 +203,9 @@ class Camera:
|
|||||||
elif self.mode == "gphoto2":
|
elif self.mode == "gphoto2":
|
||||||
data = self._preview_gphoto2()
|
data = self._preview_gphoto2()
|
||||||
if data:
|
if data:
|
||||||
|
self.preview_dslr_ok = True
|
||||||
return data
|
return data
|
||||||
|
self.preview_dslr_ok = False
|
||||||
# Fallback webcam pour le preview si le DSLR ne supporte pas le live view
|
# Fallback webcam pour le preview si le DSLR ne supporte pas le live view
|
||||||
if not self._preview_webcam_fallback:
|
if not self._preview_webcam_fallback:
|
||||||
self._init_preview_fallback()
|
self._init_preview_fallback()
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ clients_ws: list[WebSocket] = []
|
|||||||
|
|
||||||
capture_en_cours = False
|
capture_en_cours = False
|
||||||
_dslr_erreurs = 0
|
_dslr_erreurs = 0
|
||||||
|
_dernier_envoi_erreur_preview = 0.0 # timestamp du dernier camera_erreur envoye depuis le preview
|
||||||
|
|
||||||
async def surveiller_dslr():
|
async def surveiller_dslr():
|
||||||
"""Verifie periodiquement si un DSLR est branche et bascule dessus."""
|
"""Verifie periodiquement si un DSLR est branche et bascule dessus."""
|
||||||
@@ -529,6 +530,8 @@ async def traiter_message_ws(msg: dict, ws: WebSocket):
|
|||||||
if type_msg == "ping":
|
if type_msg == "ping":
|
||||||
await ws.send_json({"type": "pong"})
|
await ws.send_json({"type": "pong"})
|
||||||
elif type_msg == "preview":
|
elif type_msg == "preview":
|
||||||
|
import time as _time
|
||||||
|
global _dernier_envoi_erreur_preview
|
||||||
# Pendant une capture DSLR, utiliser directement la webcam fallback
|
# Pendant une capture DSLR, utiliser directement la webcam fallback
|
||||||
donnees = None
|
donnees = None
|
||||||
if capture_en_cours and camera._preview_webcam_fallback:
|
if capture_en_cours and camera._preview_webcam_fallback:
|
||||||
@@ -541,6 +544,12 @@ async def traiter_message_ws(msg: dict, ws: WebSocket):
|
|||||||
if donnees:
|
if donnees:
|
||||||
b64 = base64.b64encode(donnees).decode("ascii")
|
b64 = base64.b64encode(donnees).decode("ascii")
|
||||||
await ws.send_json({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"})
|
await ws.send_json({"type": "preview", "image": f"data:image/jpeg;base64,{b64}"})
|
||||||
|
# Si le DSLR est cense etre connecte mais le preview echoue, signaler l'erreur
|
||||||
|
if camera.mode == "gphoto2" and not camera.preview_dslr_ok:
|
||||||
|
now = _time.monotonic()
|
||||||
|
if now - _dernier_envoi_erreur_preview > 5.0:
|
||||||
|
_dernier_envoi_erreur_preview = now
|
||||||
|
await ws.send_json({"type": "camera_erreur", "message": "Le DSLR ne repond pas au preview"})
|
||||||
|
|
||||||
|
|
||||||
async def diffuser_ws(message: dict):
|
async def diffuser_ws(message: dict):
|
||||||
|
|||||||
@@ -303,6 +303,41 @@ html, body {
|
|||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preview-erreur-camera {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0; bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.85);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
color: #fff;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-erreur-camera.cache {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-erreur-icon {
|
||||||
|
font-size: 5rem;
|
||||||
|
animation: pulse-texte 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-erreur-camera p {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-erreur-camera span {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
opacity: 0.85;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
.compte-a-rebours {
|
.compte-a-rebours {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0; left: 0;
|
top: 0; left: 0;
|
||||||
|
|||||||
@@ -63,6 +63,11 @@
|
|||||||
<section id="ecran-capture" class="ecran">
|
<section id="ecran-capture" class="ecran">
|
||||||
<div id="preview-live" class="preview-live">
|
<div id="preview-live" class="preview-live">
|
||||||
<img id="img-preview" src="" alt="Preview">
|
<img id="img-preview" src="" alt="Preview">
|
||||||
|
<div id="preview-erreur-camera" class="preview-erreur-camera cache">
|
||||||
|
<div class="preview-erreur-icon">📷</div>
|
||||||
|
<p>Appareil photo deconnecte</p>
|
||||||
|
<span>Reconnexion en cours...</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="compte-a-rebours" class="compte-a-rebours cache">
|
<div id="compte-a-rebours" class="compte-a-rebours cache">
|
||||||
<span id="chiffre-car">3</span>
|
<span id="chiffre-car">3</span>
|
||||||
|
|||||||
@@ -2,18 +2,55 @@
|
|||||||
|
|
||||||
let previewActif = false;
|
let previewActif = false;
|
||||||
let previewInterval = null;
|
let previewInterval = null;
|
||||||
|
let previewTimeoutId = null;
|
||||||
|
const PREVIEW_TIMEOUT_MS = 3000;
|
||||||
|
|
||||||
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||||
|
|
||||||
// --- Preview live ---
|
// --- Preview live ---
|
||||||
|
|
||||||
|
function afficherErreurCapture(titre, detail = '') {
|
||||||
|
arreterPreview();
|
||||||
|
const el = document.getElementById('preview-erreur-camera');
|
||||||
|
if (!el) { allerA('accueil'); return; }
|
||||||
|
el.querySelector('p').textContent = titre;
|
||||||
|
el.querySelector('span').textContent = detail || 'Retour dans 4 secondes...';
|
||||||
|
el.classList.remove('cache');
|
||||||
|
setTimeout(() => {
|
||||||
|
el.classList.add('cache');
|
||||||
|
allerA('accueil');
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function afficherErreurPreview(visible) {
|
||||||
|
const el = document.getElementById('preview-erreur-camera');
|
||||||
|
if (!el) return;
|
||||||
|
if (visible) {
|
||||||
|
el.querySelector('p').textContent = 'Appareil photo deconnecte';
|
||||||
|
el.querySelector('span').textContent = 'Reconnexion en cours...';
|
||||||
|
el.classList.remove('cache');
|
||||||
|
} else {
|
||||||
|
el.classList.add('cache');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetPreviewTimeout() {
|
||||||
|
if (previewTimeoutId) clearTimeout(previewTimeoutId);
|
||||||
|
previewTimeoutId = setTimeout(() => {
|
||||||
|
if (previewActif) afficherErreurPreview(true);
|
||||||
|
}, PREVIEW_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
function lancerPreview() {
|
function lancerPreview() {
|
||||||
if (previewActif) return;
|
if (previewActif) return;
|
||||||
previewActif = true;
|
previewActif = true;
|
||||||
|
afficherErreurPreview(false);
|
||||||
|
|
||||||
previewInterval = setInterval(() => {
|
previewInterval = setInterval(() => {
|
||||||
if (previewActif) wsEnvoyer({ type: 'preview' });
|
if (previewActif) wsEnvoyer({ type: 'preview' });
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
resetPreviewTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
function arreterPreview() {
|
function arreterPreview() {
|
||||||
@@ -22,12 +59,28 @@ function arreterPreview() {
|
|||||||
clearInterval(previewInterval);
|
clearInterval(previewInterval);
|
||||||
previewInterval = null;
|
previewInterval = null;
|
||||||
}
|
}
|
||||||
|
if (previewTimeoutId) {
|
||||||
|
clearTimeout(previewTimeoutId);
|
||||||
|
previewTimeoutId = null;
|
||||||
|
}
|
||||||
|
afficherErreurPreview(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
wsOnMessage('preview', (msg) => {
|
wsOnMessage('preview', (msg) => {
|
||||||
if (!previewActif) return;
|
if (!previewActif) return;
|
||||||
const img = document.getElementById('img-preview');
|
const img = document.getElementById('img-preview');
|
||||||
if (img) img.src = msg.image;
|
if (img) img.src = msg.image;
|
||||||
|
afficherErreurPreview(false);
|
||||||
|
resetPreviewTimeout();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Erreur camera pendant la preview
|
||||||
|
wsOnMessage('camera_erreur', () => {
|
||||||
|
if (previewActif) afficherErreurPreview(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
wsOnMessage('camera_ok', () => {
|
||||||
|
afficherErreurPreview(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- Capture ---
|
// --- Capture ---
|
||||||
@@ -36,17 +89,27 @@ async function lancerCapture() {
|
|||||||
photosSession = [];
|
photosSession = [];
|
||||||
photoImpression = null;
|
photoImpression = null;
|
||||||
|
|
||||||
// Vider les aperçus precedents
|
|
||||||
document.getElementById('photo-resultat').src = '';
|
document.getElementById('photo-resultat').src = '';
|
||||||
document.getElementById('photo-partage').src = '';
|
document.getElementById('photo-partage').src = '';
|
||||||
|
|
||||||
majCompteurAccueil();
|
majCompteurAccueil();
|
||||||
|
|
||||||
|
// Verifier l'etat de la camera avant de commencer
|
||||||
|
try {
|
||||||
|
const statut = await apiGet('/api/camera/statut');
|
||||||
|
if (!statut.connectee || statut.mode === 'erreur') {
|
||||||
|
afficherErreurCapture('Appareil photo non disponible', 'Verifiez la connexion USB du DSLR');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
afficherErreurCapture('Impossible de joindre le serveur', 'Verifiez que le photobooth est bien demarre');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const nbPhotos = getNombrePhotos();
|
const nbPhotos = getNombrePhotos();
|
||||||
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++) {
|
||||||
// Lancer la preview pour que l'utilisateur se voit
|
|
||||||
lancerPreview();
|
lancerPreview();
|
||||||
await pause(500);
|
await pause(500);
|
||||||
|
|
||||||
@@ -54,29 +117,48 @@ async function lancerCapture() {
|
|||||||
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compte a rebours par dessus la preview
|
|
||||||
await compteARebours();
|
await compteARebours();
|
||||||
|
|
||||||
// Arreter la preview et attendre que le DSLR soit libre
|
// Si la camera a signale une erreur pendant le compte a rebours, on arrete
|
||||||
|
const erreurVisible = !document.getElementById('preview-erreur-camera').classList.contains('cache');
|
||||||
|
if (erreurVisible) {
|
||||||
|
arreterPreview();
|
||||||
|
afficherErreurCapture('Appareil photo deconnecte', 'Reconnectez le DSLR et reessayez');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
arreterPreview();
|
arreterPreview();
|
||||||
await pause(300);
|
await pause(300);
|
||||||
|
|
||||||
afficherFlash();
|
afficherFlash();
|
||||||
|
|
||||||
// Tenter la capture avec retry
|
// Tenter la capture avec retry
|
||||||
let resultat = null;
|
let resultat = null;
|
||||||
|
let erreurReseau = false;
|
||||||
for (let r = 0; r < 3; r++) {
|
for (let r = 0; r < 3; r++) {
|
||||||
resultat = await apiPost('/api/capturer');
|
try {
|
||||||
|
resultat = await apiPost('/api/capturer');
|
||||||
|
} catch (e) {
|
||||||
|
erreurReseau = true;
|
||||||
|
await pause(500);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
erreurReseau = false;
|
||||||
if (!resultat.erreur) break;
|
if (!resultat.erreur) break;
|
||||||
if (resultat.erreur === 'Limite de photos atteinte') {
|
if (resultat.erreur === 'Limite de photos atteinte') {
|
||||||
allerA('accueil');
|
afficherErreurCapture('Limite de photos atteinte', 'La session est terminee');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await pause(500);
|
await pause(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resultat.erreur) {
|
if (erreurReseau) {
|
||||||
allerA('accueil');
|
afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultat || resultat.erreur) {
|
||||||
|
const msg = resultat?.erreur || 'Erreur inconnue';
|
||||||
|
afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,30 +276,43 @@ let photoImpression = null; // Nom du fichier d'impression (2 bandes sur 10x15)
|
|||||||
|
|
||||||
async function traiterCapture() {
|
async function traiterCapture() {
|
||||||
if (photosSession.length === 0) {
|
if (photosSession.length === 0) {
|
||||||
allerA('accueil');
|
afficherErreurCapture('Aucune photo capturee', 'Une erreur inattendue s\'est produite');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
photoImpression = null;
|
photoImpression = null;
|
||||||
|
|
||||||
if (modeActuel === 'multi') {
|
try {
|
||||||
const mode = config.multi_shot?.mode || 'strip';
|
if (modeActuel === 'multi') {
|
||||||
let resultat;
|
const mode = config.multi_shot?.mode || 'strip';
|
||||||
if (mode === 'strip') {
|
let resultat;
|
||||||
resultat = await apiPost('/api/strip', { photos: photosSession });
|
if (mode === 'strip') {
|
||||||
if (resultat.impression) {
|
resultat = await apiPost('/api/strip', { photos: photosSession });
|
||||||
photoImpression = resultat.impression;
|
if (resultat.erreur) {
|
||||||
|
afficherErreurCapture('Erreur creation de la planche', resultat.erreur);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (resultat.impression) photoImpression = resultat.impression;
|
||||||
|
} else {
|
||||||
|
resultat = await apiPost('/api/collage', { photos: photosSession });
|
||||||
|
if (resultat.erreur) {
|
||||||
|
afficherErreurCapture('Erreur creation du collage', resultat.erreur);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!resultat.nom) {
|
||||||
|
afficherErreurCapture('Erreur traitement des photos', 'Le fichier final n\'a pas ete genere');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
resultat = await apiPost('/api/collage', { photos: photosSession });
|
|
||||||
}
|
|
||||||
if (resultat.nom) {
|
|
||||||
photoFinale = resultat.nom;
|
photoFinale = resultat.nom;
|
||||||
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
||||||
|
} else {
|
||||||
|
photoFinale = photosSession[0];
|
||||||
|
afficherPreviewPhoto('/data/photos/' + photosSession[0]);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (e) {
|
||||||
photoFinale = photosSession[0];
|
afficherErreurCapture('Erreur reseau', 'Impossible de traiter les photos');
|
||||||
afficherPreviewPhoto('/data/photos/' + photosSession[0]);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
allerA('preview');
|
allerA('preview');
|
||||||
|
|||||||
Reference in New Issue
Block a user