Init photobooth - borne photo evenementielle RPi4
Backend FastAPI complet : camera gphoto2, filtres/overlays Pillow, chroma key OpenCV, multi-shot/collage, GIF, impression CUPS, email SMTP, QR code. Frontend web vanilla (HTML/CSS/JS) pour Chromium kiosk. Menu admin cache (appui long coin ecran). Toutes les fonctionnalites activables/desactivables. Scripts install + systemd pour RPi4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
158
frontend/js/camera.js
Normal file
158
frontend/js/camera.js
Normal file
@@ -0,0 +1,158 @@
|
||||
/* Module capture photo - Preview live, compte a rebours, declenchement */
|
||||
|
||||
let previewActif = false;
|
||||
let previewInterval = null;
|
||||
|
||||
// --- Preview live ---
|
||||
|
||||
function lancerPreview() {
|
||||
if (previewActif) return;
|
||||
previewActif = true;
|
||||
|
||||
// Demander des previews via WebSocket
|
||||
previewInterval = setInterval(() => {
|
||||
if (previewActif) wsEnvoyer({ type: 'preview' });
|
||||
}, 100); // ~10 fps
|
||||
}
|
||||
|
||||
function arreterPreview() {
|
||||
previewActif = false;
|
||||
if (previewInterval) {
|
||||
clearInterval(previewInterval);
|
||||
previewInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Recevoir les previews
|
||||
wsOnMessage('preview', (msg) => {
|
||||
if (!previewActif) return;
|
||||
const img = document.getElementById('img-preview');
|
||||
if (img) img.src = msg.image;
|
||||
});
|
||||
|
||||
// --- Capture ---
|
||||
|
||||
async function lancerCapture() {
|
||||
photosSession = [];
|
||||
lancerPreview();
|
||||
|
||||
const nbPhotos = getNombrePhotos();
|
||||
const compteurEl = document.getElementById('capture-compteur');
|
||||
|
||||
for (let i = 0; i < nbPhotos; i++) {
|
||||
if (nbPhotos > 1) {
|
||||
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
||||
}
|
||||
|
||||
// Compte a rebours
|
||||
await compteARebours();
|
||||
|
||||
// Flash
|
||||
afficherFlash();
|
||||
|
||||
// Capture
|
||||
arreterPreview();
|
||||
const resultat = await apiPost('/api/capturer');
|
||||
if (resultat.nom) {
|
||||
photosSession.push(resultat.nom);
|
||||
}
|
||||
|
||||
// Reprendre le preview si encore des photos a prendre
|
||||
if (i < nbPhotos - 1) {
|
||||
lancerPreview();
|
||||
await pause(500);
|
||||
}
|
||||
}
|
||||
|
||||
// Traitement selon le mode
|
||||
await traiterCapture();
|
||||
}
|
||||
|
||||
function getNombrePhotos() {
|
||||
if (modeActuel === 'simple') return 1;
|
||||
if (modeActuel === 'multi') return config.multi_shot?.nombre_photos || 3;
|
||||
if (modeActuel === 'gif') return config.gif?.nombre_frames || 4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function compteARebours() {
|
||||
const conteneur = document.getElementById('compte-a-rebours');
|
||||
const chiffre = document.getElementById('chiffre-car');
|
||||
const duree = config.camera?.compte_a_rebours || 3;
|
||||
|
||||
conteneur.classList.remove('cache');
|
||||
|
||||
for (let i = duree; i > 0; i--) {
|
||||
chiffre.textContent = i;
|
||||
// Re-trigger animation
|
||||
chiffre.style.animation = 'none';
|
||||
chiffre.offsetHeight; // force reflow
|
||||
chiffre.style.animation = 'pop 0.5s ease';
|
||||
await pause(1000);
|
||||
}
|
||||
|
||||
conteneur.classList.add('cache');
|
||||
}
|
||||
|
||||
function afficherFlash() {
|
||||
const flash = document.getElementById('flash-blanc');
|
||||
flash.classList.remove('cache');
|
||||
flash.style.animation = 'none';
|
||||
flash.offsetHeight;
|
||||
flash.style.animation = 'flash 0.3s ease-out forwards';
|
||||
setTimeout(() => flash.classList.add('cache'), 400);
|
||||
}
|
||||
|
||||
async function traiterCapture() {
|
||||
if (photosSession.length === 0) {
|
||||
allerA('accueil');
|
||||
return;
|
||||
}
|
||||
|
||||
if (modeActuel === 'gif') {
|
||||
// Creer le GIF
|
||||
const resultat = await apiPost('/api/gif', { photos: photosSession });
|
||||
if (resultat.nom) {
|
||||
photoFinale = resultat.nom;
|
||||
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
||||
}
|
||||
} else if (modeActuel === 'multi') {
|
||||
// Creer le strip/collage
|
||||
const mode = config.multi_shot?.mode || 'strip';
|
||||
let resultat;
|
||||
if (mode === 'strip') {
|
||||
resultat = await apiPost('/api/strip', { photos: photosSession });
|
||||
} else {
|
||||
resultat = await apiPost('/api/collage', { photos: photosSession });
|
||||
}
|
||||
if (resultat.nom) {
|
||||
photoFinale = resultat.nom;
|
||||
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
||||
}
|
||||
} else {
|
||||
// Photo simple - aller au preview avec filtres
|
||||
photoFinale = photosSession[0];
|
||||
afficherPreviewPhoto('/data/photos/' + photosSession[0]);
|
||||
}
|
||||
|
||||
allerA('preview');
|
||||
}
|
||||
|
||||
function afficherPreviewPhoto(chemin) {
|
||||
document.getElementById('photo-resultat').src = chemin;
|
||||
document.getElementById('photo-partage').src = chemin;
|
||||
|
||||
// Charger les filtres si photo simple
|
||||
if (modeActuel === 'simple' && config.fonctionnalites?.filtres) {
|
||||
chargerFiltres();
|
||||
}
|
||||
|
||||
// Charger les overlays
|
||||
if (config.fonctionnalites?.overlays) {
|
||||
chargerOverlays();
|
||||
}
|
||||
}
|
||||
|
||||
function pause(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
Reference in New Issue
Block a user