#!/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()