Compare commits
5 Commits
9c57c8f5f2
...
6451f5774c
| Author | SHA1 | Date | |
|---|---|---|---|
| 6451f5774c | |||
| 1120513309 | |||
| 46752bf29b | |||
| eb0634fab4 | |||
| 529cb41581 |
BIN
frontend/assets/logos/copydev.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
@@ -753,22 +753,30 @@ class PB:
|
|||||||
# ── Thread caméra ────────────────────────────────────────────────────────
|
# ── Thread caméra ────────────────────────────────────────────────────────
|
||||||
def _cam_loop(self):
|
def _cam_loop(self):
|
||||||
errs = 0
|
errs = 0
|
||||||
|
post_capture_grace = 0 # frames de grâce après une prise (ignore erreurs transitoires)
|
||||||
while True:
|
while True:
|
||||||
# En simulation : tentative de connexion automatique toutes les 10s
|
# En simulation : tentative de connexion automatique toutes les 15s
|
||||||
|
# Pas de USB reset ici — reset seulement en cas de reconnexion
|
||||||
if self.sim:
|
if self.sim:
|
||||||
time.sleep(10)
|
time.sleep(15)
|
||||||
_usb_reset_done()
|
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
try:
|
try:
|
||||||
c = gp.Camera(); c.init()
|
c = gp.Camera(); c.init()
|
||||||
|
# Vérification fonctionnelle : un preview doit réussir
|
||||||
|
_f = c.capture_preview(); _bytes = bytes(_f.get_data_and_size())
|
||||||
if self.cam:
|
if self.cam:
|
||||||
try: self.cam.exit()
|
try: self.cam.exit()
|
||||||
except: pass
|
except: pass
|
||||||
self.cam = c; self.sim = False; self.prev_actif = True
|
self.cam = c; self.sim = False; self.prev_actif = True
|
||||||
|
surf = pygame.image.load(io.BytesIO(_bytes))
|
||||||
|
surf = pygame.transform.scale(surf, (W, H))
|
||||||
|
with self.flock: self.frame = surf
|
||||||
print("DSLR détecté automatiquement")
|
print("DSLR détecté automatiquement")
|
||||||
break
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Auto-detect caméra: {e}")
|
print(f"Auto-detect caméra: {e}")
|
||||||
|
try: c.exit()
|
||||||
|
except: pass
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
continue
|
continue
|
||||||
if self.prev_actif:
|
if self.prev_actif:
|
||||||
@@ -776,77 +784,69 @@ class PB:
|
|||||||
if self.cam is None:
|
if self.cam is None:
|
||||||
raise RuntimeError("camera None")
|
raise RuntimeError("camera None")
|
||||||
if self.cap_demande:
|
if self.cap_demande:
|
||||||
# Déclenchement sans mouvement miroir :
|
# Le liveview est actif (capture_preview() tourne) → miroir DÉJÀ levé
|
||||||
# eosremoterelease garde le LV actif sur Canon EOS
|
# trigger_capture() en liveview = obturateur seul, pas de mouvement miroir
|
||||||
triggered = False
|
print("Capture demandée — trigger_capture...")
|
||||||
try:
|
self.cam.trigger_capture()
|
||||||
cfg_c = self.cam.get_config()
|
|
||||||
rl = cfg_c.get_child_by_name('eosremoterelease')
|
|
||||||
rl.set_value('Immediate')
|
|
||||||
self.cam.set_config(cfg_c)
|
|
||||||
triggered = True
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
if not triggered:
|
|
||||||
self.cam.trigger_capture()
|
|
||||||
ch = None
|
ch = None
|
||||||
deadline = time.time() + 15
|
deadline = time.time() + 12
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
ev_type, ev_data = self.cam.wait_for_event(500)
|
ev_type, ev_data = self.cam.wait_for_event(200)
|
||||||
if ev_type == gp.GP_EVENT_FILE_ADDED:
|
if ev_type == gp.GP_EVENT_FILE_ADDED:
|
||||||
|
print(f"Fichier reçu : {ev_data.name}")
|
||||||
cf = gp.CameraFile()
|
cf = gp.CameraFile()
|
||||||
self.cam.file_get(ev_data.folder, ev_data.name, gp.GP_FILE_TYPE_NORMAL, cf)
|
self.cam.file_get(ev_data.folder, ev_data.name, gp.GP_FILE_TYPE_NORMAL, cf)
|
||||||
nom = f"photo_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}.jpg"
|
nom = f"photo_{datetime.now().strftime('%Y%m%d_%H%M%S_%f')}.jpg"
|
||||||
ch = str(PHOTOS_DIR/nom); cf.save(ch); break
|
ch = str(PHOTOS_DIR/nom); cf.save(ch)
|
||||||
# Réinitialise le remote release
|
print(f"Photo sauvegardée : {nom}"); break
|
||||||
try:
|
if ch is None:
|
||||||
cfg_c = self.cam.get_config()
|
print("AVERTISSEMENT : aucun fichier reçu dans les 12s")
|
||||||
rl = cfg_c.get_child_by_name('eosremoterelease')
|
|
||||||
rl.set_value('None')
|
|
||||||
self.cam.set_config(cfg_c)
|
|
||||||
except: pass
|
|
||||||
self.cap_result=ch; self.cap_demande=False; self.cap_done.set()
|
self.cap_result=ch; self.cap_demande=False; self.cap_done.set()
|
||||||
errs = 0
|
errs = 0; post_capture_grace = 8
|
||||||
else:
|
else:
|
||||||
f=self.cam.capture_preview(); data=bytes(f.get_data_and_size())
|
f=self.cam.capture_preview(); data=bytes(f.get_data_and_size())
|
||||||
surf=pygame.image.load(io.BytesIO(data))
|
surf=pygame.image.load(io.BytesIO(data))
|
||||||
surf=pygame.transform.scale(surf,(W,H))
|
surf=pygame.transform.scale(surf,(W,H))
|
||||||
with self.flock: self.frame=surf
|
with self.flock: self.frame=surf
|
||||||
errs = 0
|
errs = 0
|
||||||
|
if post_capture_grace > 0:
|
||||||
|
post_capture_grace -= 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
errs += 1
|
if post_capture_grace > 0:
|
||||||
print(f"cam erreur ({errs}): {e}")
|
# Erreurs transitoires post-capture normales sur EOS — on ignore
|
||||||
if self.cap_demande and errs >= 3:
|
post_capture_grace -= 1
|
||||||
# abandon de la capture en cours
|
print(f"cam post-capture ({post_capture_grace} restants): {e}")
|
||||||
self.cap_result=None; self.cap_demande=False; self.cap_done.set()
|
time.sleep(0.2)
|
||||||
if errs >= 5:
|
|
||||||
# reconnexion automatique
|
|
||||||
print("Caméra perdue — tentative de reconnexion...")
|
|
||||||
self.prev_actif = False
|
|
||||||
with self.flock: self.frame = None
|
|
||||||
try:
|
|
||||||
if self.cam: self.cam.exit()
|
|
||||||
except: pass
|
|
||||||
self.cam = None; errs = 0
|
|
||||||
_nb_retry = 0
|
|
||||||
while _nb_retry < 10:
|
|
||||||
_nb_retry += 1
|
|
||||||
time.sleep(3)
|
|
||||||
_usb_reset_done()
|
|
||||||
try:
|
|
||||||
c = gp.Camera(); c.init()
|
|
||||||
self.cam = c; self.prev_actif = True
|
|
||||||
print("Caméra reconnectée automatiquement")
|
|
||||||
break
|
|
||||||
except Exception as re:
|
|
||||||
print(f"Reconnexion échouée ({_nb_retry}): {re}")
|
|
||||||
else:
|
else:
|
||||||
# 10 tentatives échouées → bascule en simulation
|
errs += 1
|
||||||
self.sim = True
|
print(f"cam erreur ({errs}): {e}")
|
||||||
print("Caméra introuvable → mode simulation")
|
if self.cap_demande and errs >= 8:
|
||||||
errs = 0
|
# abandon de la capture seulement après 8 erreurs consécutives
|
||||||
else:
|
self.cap_result=None; self.cap_demande=False; self.cap_done.set()
|
||||||
time.sleep(0.3)
|
if errs >= 10:
|
||||||
|
# reconnexion automatique
|
||||||
|
print("Caméra perdue — tentative de reconnexion...")
|
||||||
|
self.prev_actif = False
|
||||||
|
with self.flock: self.frame = None
|
||||||
|
try:
|
||||||
|
if self.cam: self.cam.exit()
|
||||||
|
except: pass
|
||||||
|
self.cam = None; errs = 0
|
||||||
|
for _nb_retry in range(10):
|
||||||
|
time.sleep(3)
|
||||||
|
_usb_reset_done()
|
||||||
|
try:
|
||||||
|
c = gp.Camera(); c.init()
|
||||||
|
self.cam = c; self.prev_actif = True
|
||||||
|
print("Caméra reconnectée automatiquement")
|
||||||
|
break
|
||||||
|
except Exception as re:
|
||||||
|
print(f"Reconnexion échouée ({_nb_retry+1}): {re}")
|
||||||
|
else:
|
||||||
|
self.sim = True
|
||||||
|
print("Caméra introuvable → mode simulation")
|
||||||
|
else:
|
||||||
|
time.sleep(0.2)
|
||||||
else:
|
else:
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
@@ -875,10 +875,12 @@ class PB:
|
|||||||
thumb_h = _PELL_FH if is_pell else 200 # haute résolution en mode pellicule
|
thumb_h = _PELL_FH if is_pell else 200 # haute résolution en mode pellicule
|
||||||
|
|
||||||
for i in range(nb):
|
for i in range(nb):
|
||||||
self.num_ph=i; self.etat=S_PREV; time.sleep(2.5)
|
self.num_ph=i; self.etat=S_PREV; time.sleep(1.5)
|
||||||
self.etat=S_COMPTE
|
self.etat=S_COMPTE
|
||||||
for c in range(delai,0,-1): self.chiffre=c; time.sleep(1)
|
for c in range(delai,0,-1): self.chiffre=c; time.sleep(1)
|
||||||
|
print(f"Pose {i+1}/{nb} — déclenchement")
|
||||||
self.etat=S_CAP; ch=self._capturer()
|
self.etat=S_CAP; ch=self._capturer()
|
||||||
|
print(f"Pose {i+1}/{nb} — résultat : {ch}")
|
||||||
if ch:
|
if ch:
|
||||||
self.photos.append(ch); self.compteur+=1; self._sav_cpt()
|
self.photos.append(ch); self.compteur+=1; self._sav_cpt()
|
||||||
try:
|
try:
|
||||||
@@ -886,7 +888,11 @@ class PB:
|
|||||||
self._prev_thumbs.append(pygame.image.fromstring(img.tobytes(), img.size, "RGB"))
|
self._prev_thumbs.append(pygame.image.fromstring(img.tobytes(), img.size, "RGB"))
|
||||||
except:
|
except:
|
||||||
self._prev_thumbs.append(None)
|
self._prev_thumbs.append(None)
|
||||||
time.sleep(0.4)
|
else:
|
||||||
|
self._prev_thumbs.append(None) # placeholder pour garder l'index cohérent
|
||||||
|
self.etat=S_PREV # repasse en preview pour montrer la vignette
|
||||||
|
time.sleep(0.6)
|
||||||
|
print(f"Séquence terminée — {len(self.photos)}/{nb} photos")
|
||||||
if not self.photos: self.etat=S_ACCUEIL; return
|
if not self.photos: self.etat=S_ACCUEIL; return
|
||||||
self._upload_booth_bg(self.photos) # upload + QR session en arrière-plan
|
self._upload_booth_bg(self.photos) # upload + QR session en arrière-plan
|
||||||
if self.mode=="simple":
|
if self.mode=="simple":
|
||||||
@@ -1249,7 +1255,7 @@ class PB:
|
|||||||
else:
|
else:
|
||||||
with self.flock:
|
with self.flock:
|
||||||
if self.frame:
|
if self.frame:
|
||||||
lv = pygame.transform.scale(self.frame, (fw, fh))
|
lv = _pfit(self.frame, fw, fh)
|
||||||
self.scr.blit(lv, (fx, fy))
|
self.scr.blit(lv, (fx, fy))
|
||||||
else:
|
else:
|
||||||
pygame.draw.rect(self.scr, (25, 25, 25), (fx, fy, fw, fh))
|
pygame.draw.rect(self.scr, (25, 25, 25), (fx, fy, fw, fh))
|
||||||
@@ -1354,7 +1360,11 @@ class PB:
|
|||||||
self.scr.blit(info,(W//2-info.get_width()//2,H-90))
|
self.scr.blit(info,(W//2-info.get_width()//2,H-90))
|
||||||
|
|
||||||
def draw_capture(self):
|
def draw_capture(self):
|
||||||
self._lv(); self._overlay(90)
|
if self._is_pell():
|
||||||
|
self._draw_pellicule(show_live=False)
|
||||||
|
self._overlay(70)
|
||||||
|
else:
|
||||||
|
self._lv(); self._overlay(90)
|
||||||
s=self._txt("Prise de vue...","md"); self._c(s,H//2-s.get_height()//2)
|
s=self._txt("Prise de vue...","md"); self._c(s,H//2-s.get_height()//2)
|
||||||
|
|
||||||
# ── Galerie ───────────────────────────────────────────────────────────────
|
# ── Galerie ───────────────────────────────────────────────────────────────
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
scripts/grub-theme/icons/uefi-firmware.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 493 B |
|
Before Width: | Height: | Size: 93 B After Width: | Height: | Size: 99 B |
|
Before Width: | Height: | Size: 92 B After Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 78 B After Width: | Height: | Size: 78 B |
|
Before Width: | Height: | Size: 78 B After Width: | Height: | Size: 78 B |
|
Before Width: | Height: | Size: 92 B After Width: | Height: | Size: 102 B |
|
Before Width: | Height: | Size: 78 B After Width: | Height: | Size: 78 B |
|
Before Width: | Height: | Size: 78 B After Width: | Height: | Size: 78 B |
|
Before Width: | Height: | Size: 93 B After Width: | Height: | Size: 99 B |
@@ -1,35 +1,25 @@
|
|||||||
# Theme GRUB tactile - Triple Boot Terra
|
# Theme GRUB tactile - Triple Boot Terra
|
||||||
title-text: ""
|
title-text: ""
|
||||||
desktop-color: "#1a1a2e"
|
desktop-image: "background.png"
|
||||||
|
desktop-color: "#0f1428"
|
||||||
message-color: "#ffffff"
|
message-color: "#ffffff"
|
||||||
message-bg-color: "#000000"
|
message-bg-color: "#000000"
|
||||||
terminal-font: "DejaVu Sans Bold 24"
|
terminal-font: "DejaVu Sans Bold 24"
|
||||||
|
|
||||||
+ boot_menu {
|
+ boot_menu {
|
||||||
left = 10%
|
left = 15%
|
||||||
top = 15%
|
top = 25%
|
||||||
width = 80%
|
width = 70%
|
||||||
height = 70%
|
height = 60%
|
||||||
item_font = "DejaVu Sans Bold 36"
|
item_font = "DejaVu Sans Bold 48"
|
||||||
item_color = "#cccccc"
|
item_color = "#aabbcc"
|
||||||
selected_item_font = "DejaVu Sans Bold 42"
|
selected_item_font = "DejaVu Sans Bold 48"
|
||||||
selected_item_color = "#ffffff"
|
selected_item_color = "#ffffff"
|
||||||
item_height = 120
|
item_height = 160
|
||||||
item_padding = 20
|
item_padding = 30
|
||||||
item_spacing = 30
|
item_spacing = 20
|
||||||
item_icon_space = 30
|
item_icon_space = 40
|
||||||
icon_width = 96
|
icon_width = 128
|
||||||
icon_height = 96
|
icon_height = 128
|
||||||
selected_item_pixmap_style = "select_*.png"
|
selected_item_pixmap_style = "select_*.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
+ label {
|
|
||||||
left = 0
|
|
||||||
top = 90%
|
|
||||||
width = 100%
|
|
||||||
height = 30
|
|
||||||
text = "Toucher pour selectionner - Appuyer sur Entree pour demarrer"
|
|
||||||
font = "DejaVu Sans 18"
|
|
||||||
color = "#888888"
|
|
||||||
align = "center"
|
|
||||||
}
|
|
||||||
|
|||||||
242
scripts/test_photobooth_pygame.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Prototype photobooth Pygame.
|
||||||
|
- Bouton -> liveview -> 3 2 1 -> photo x4 -> grille 4 photos
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
import pygame
|
||||||
|
import gphoto2 as gp
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Config
|
||||||
|
LARGEUR, HAUTEUR = 1920, 1280
|
||||||
|
NB_PHOTOS = 4
|
||||||
|
DUREE_COMPTE = 3
|
||||||
|
DOSSIER_PHOTOS = Path("/tmp/photobooth_test")
|
||||||
|
DOSSIER_PHOTOS.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
# Couleurs
|
||||||
|
NOIR = (0, 0, 0)
|
||||||
|
BLANC = (255, 255, 255)
|
||||||
|
ROSE = (233, 30, 99)
|
||||||
|
GRIS = (40, 40, 40)
|
||||||
|
|
||||||
|
class Photobooth:
|
||||||
|
def __init__(self):
|
||||||
|
pygame.init()
|
||||||
|
self.screen = pygame.display.set_mode((LARGEUR, HAUTEUR), pygame.FULLSCREEN)
|
||||||
|
pygame.display.set_caption("Photobooth")
|
||||||
|
self.clock = pygame.time.Clock()
|
||||||
|
self.font_grand = pygame.font.SysFont(None, 300)
|
||||||
|
self.font_moyen = pygame.font.SysFont(None, 100)
|
||||||
|
self.font_petit = pygame.font.SysFont(None, 60)
|
||||||
|
|
||||||
|
# Camera
|
||||||
|
self.cam = gp.Camera()
|
||||||
|
self.cam.init()
|
||||||
|
|
||||||
|
# Etat
|
||||||
|
self.etat = "accueil" # accueil, preview, compte, capture, galerie
|
||||||
|
self.frame_courante = None
|
||||||
|
self.frame_lock = threading.Lock()
|
||||||
|
self.preview_actif = False
|
||||||
|
|
||||||
|
# Capture via le thread preview (evite le baisse de miroir)
|
||||||
|
self.capture_demande = False
|
||||||
|
self.capture_resultat = None
|
||||||
|
self.capture_done = threading.Event()
|
||||||
|
self.photos = []
|
||||||
|
self.num_photo = 0
|
||||||
|
self.chiffre_compte = 0
|
||||||
|
|
||||||
|
# Thread preview
|
||||||
|
self.thread_preview = threading.Thread(target=self._loop_preview, daemon=True)
|
||||||
|
self.thread_preview.start()
|
||||||
|
|
||||||
|
def _loop_preview(self):
|
||||||
|
while True:
|
||||||
|
if self.preview_actif:
|
||||||
|
try:
|
||||||
|
if self.capture_demande:
|
||||||
|
try:
|
||||||
|
chemin_cam = self.cam.capture(gp.GP_CAPTURE_IMAGE)
|
||||||
|
cf = gp.CameraFile()
|
||||||
|
self.cam.file_get(chemin_cam.folder, chemin_cam.name, gp.GP_FILE_TYPE_NORMAL, cf)
|
||||||
|
nom = f"photo_{datetime.now().strftime('%H%M%S_%f')}.jpg"
|
||||||
|
chemin = str(DOSSIER_PHOTOS / nom)
|
||||||
|
cf.save(chemin)
|
||||||
|
self.capture_resultat = chemin
|
||||||
|
print(f"Photo capturee: {chemin}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur capture: {e}")
|
||||||
|
self.capture_resultat = None
|
||||||
|
self.capture_demande = False
|
||||||
|
self.capture_done.set()
|
||||||
|
else:
|
||||||
|
f = self.cam.capture_preview()
|
||||||
|
data = bytes(f.get_data_and_size())
|
||||||
|
surf = pygame.image.load(io.BytesIO(data))
|
||||||
|
surf = pygame.transform.scale(surf, (LARGEUR, HAUTEUR))
|
||||||
|
with self.frame_lock:
|
||||||
|
self.frame_courante = surf
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Preview erreur: {e}")
|
||||||
|
time.sleep(0.3) # Camera peut etre occupee apres capture
|
||||||
|
else:
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
def capturer_photo(self):
|
||||||
|
"""Demande une capture au thread preview (miroir reste leve)."""
|
||||||
|
self.capture_resultat = None
|
||||||
|
self.capture_done.clear()
|
||||||
|
self.capture_demande = True
|
||||||
|
ok = self.capture_done.wait(timeout=10)
|
||||||
|
if not ok:
|
||||||
|
print("Timeout capture")
|
||||||
|
self.capture_demande = False
|
||||||
|
return self.capture_resultat
|
||||||
|
|
||||||
|
def dessiner_accueil(self):
|
||||||
|
self.screen.fill(NOIR)
|
||||||
|
txt = self.font_moyen.render("PHOTOBOOTH", True, ROSE)
|
||||||
|
self.screen.blit(txt, (LARGEUR//2 - txt.get_width()//2, 300))
|
||||||
|
|
||||||
|
# Bouton
|
||||||
|
btn = pygame.Rect(LARGEUR//2 - 300, HAUTEUR//2, 600, 150)
|
||||||
|
pygame.draw.rect(self.screen, ROSE, btn, border_radius=20)
|
||||||
|
t = self.font_moyen.render("COMMENCER", True, BLANC)
|
||||||
|
self.screen.blit(t, (btn.centerx - t.get_width()//2, btn.centery - t.get_height()//2))
|
||||||
|
return btn
|
||||||
|
|
||||||
|
def dessiner_preview(self):
|
||||||
|
with self.frame_lock:
|
||||||
|
if self.frame_courante:
|
||||||
|
self.screen.blit(self.frame_courante, (0, 0))
|
||||||
|
else:
|
||||||
|
self.screen.fill(NOIR)
|
||||||
|
# Info
|
||||||
|
t = self.font_petit.render(f"Photo {self.num_photo+1}/{NB_PHOTOS} - Souriez!", True, BLANC)
|
||||||
|
self.screen.blit(t, (20, 20))
|
||||||
|
|
||||||
|
def dessiner_compte(self):
|
||||||
|
with self.frame_lock:
|
||||||
|
if self.frame_courante:
|
||||||
|
self.screen.blit(self.frame_courante, (0, 0))
|
||||||
|
else:
|
||||||
|
self.screen.fill(NOIR)
|
||||||
|
# Overlay sombre
|
||||||
|
overlay = pygame.Surface((LARGEUR, HAUTEUR), pygame.SRCALPHA)
|
||||||
|
overlay.fill((0, 0, 0, 100))
|
||||||
|
self.screen.blit(overlay, (0, 0))
|
||||||
|
# Chiffre
|
||||||
|
txt = self.font_grand.render(str(self.chiffre_compte), True, BLANC)
|
||||||
|
self.screen.blit(txt, (LARGEUR//2 - txt.get_width()//2, HAUTEUR//2 - txt.get_height()//2))
|
||||||
|
|
||||||
|
def dessiner_galerie(self):
|
||||||
|
self.screen.fill(NOIR)
|
||||||
|
if not self.photos:
|
||||||
|
t = self.font_petit.render("Aucune photo", True, BLANC)
|
||||||
|
self.screen.blit(t, (LARGEUR//2 - t.get_width()//2, HAUTEUR//2))
|
||||||
|
else:
|
||||||
|
# 4 photos en grille 2x2
|
||||||
|
w, h = LARGEUR//2 - 20, HAUTEUR//2 - 20
|
||||||
|
positions = [(10, 10), (LARGEUR//2+10, 10), (10, HAUTEUR//2+10), (LARGEUR//2+10, HAUTEUR//2+10)]
|
||||||
|
for i, chemin in enumerate(self.photos[:4]):
|
||||||
|
if i >= len(positions):
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
# PIL pour redimensionner efficacement les grands JPEG Canon
|
||||||
|
img = Image.open(chemin).convert('RGB')
|
||||||
|
img = img.resize((w, h), Image.LANCZOS)
|
||||||
|
surf = pygame.image.fromstring(img.tobytes(), img.size, 'RGB')
|
||||||
|
self.screen.blit(surf, positions[i])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Erreur galerie photo {i}: {e}")
|
||||||
|
|
||||||
|
# Bouton recommencer
|
||||||
|
btn = pygame.Rect(LARGEUR//2 - 200, HAUTEUR - 120, 400, 100)
|
||||||
|
pygame.draw.rect(self.screen, ROSE, btn, border_radius=15)
|
||||||
|
t = self.font_petit.render("RECOMMENCER", True, BLANC)
|
||||||
|
self.screen.blit(t, (btn.centerx - t.get_width()//2, btn.centery - t.get_height()//2))
|
||||||
|
return btn
|
||||||
|
|
||||||
|
def lancer_sequence(self):
|
||||||
|
"""Lance la séquence 4 photos dans un thread."""
|
||||||
|
def sequence():
|
||||||
|
self.photos = []
|
||||||
|
for i in range(NB_PHOTOS):
|
||||||
|
self.num_photo = i
|
||||||
|
# Preview
|
||||||
|
self.preview_actif = True
|
||||||
|
self.etat = "preview"
|
||||||
|
time.sleep(1.5)
|
||||||
|
|
||||||
|
# Compte a rebours
|
||||||
|
self.etat = "compte"
|
||||||
|
for c in range(DUREE_COMPTE, 0, -1):
|
||||||
|
self.chiffre_compte = c
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Flash + capture (miroir reste leve, liveview continue)
|
||||||
|
self.etat = "capture"
|
||||||
|
chemin = self.capturer_photo()
|
||||||
|
if chemin:
|
||||||
|
self.photos.append(chemin)
|
||||||
|
print(f"Photo {i+1} : {chemin}")
|
||||||
|
time.sleep(0.3)
|
||||||
|
|
||||||
|
# Galerie
|
||||||
|
self.preview_actif = False
|
||||||
|
self.etat = "galerie"
|
||||||
|
|
||||||
|
threading.Thread(target=sequence, daemon=True).start()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
btn_accueil = None
|
||||||
|
btn_galerie = None
|
||||||
|
running = True
|
||||||
|
|
||||||
|
while running:
|
||||||
|
for event in pygame.event.get():
|
||||||
|
if event.type == pygame.QUIT:
|
||||||
|
running = False
|
||||||
|
if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
|
||||||
|
running = False
|
||||||
|
if event.type == pygame.MOUSEBUTTONDOWN:
|
||||||
|
pos = pygame.mouse.get_pos()
|
||||||
|
if self.etat == "accueil" and btn_accueil and btn_accueil.collidepoint(pos):
|
||||||
|
self.lancer_sequence()
|
||||||
|
if self.etat == "galerie" and btn_galerie and btn_galerie.collidepoint(pos):
|
||||||
|
self.etat = "accueil"
|
||||||
|
self.photos = []
|
||||||
|
|
||||||
|
if self.etat == "accueil":
|
||||||
|
btn_accueil = self.dessiner_accueil()
|
||||||
|
btn_galerie = None
|
||||||
|
elif self.etat == "preview":
|
||||||
|
self.dessiner_preview()
|
||||||
|
elif self.etat == "compte":
|
||||||
|
self.dessiner_compte()
|
||||||
|
elif self.etat == "capture":
|
||||||
|
self.dessiner_compte() # Reste sur le liveview pendant la capture
|
||||||
|
elif self.etat == "galerie":
|
||||||
|
btn_galerie = self.dessiner_galerie()
|
||||||
|
btn_accueil = None
|
||||||
|
|
||||||
|
pygame.display.flip()
|
||||||
|
self.clock.tick(30)
|
||||||
|
|
||||||
|
self.preview_actif = False
|
||||||
|
self.cam.exit()
|
||||||
|
pygame.quit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = Photobooth()
|
||||||
|
app.run()
|
||||||