diff --git a/photobooth_pygame.py b/photobooth_pygame.py index 0e43043..c83d7cd 100644 --- a/photobooth_pygame.py +++ b/photobooth_pygame.py @@ -543,6 +543,7 @@ class PB: self.cap_result = None self.cap_done = threading.Event() self.photos = [] + self._prev_thumbs = [] # miniatures pour le strip multipose self.num_ph = 0 self.chiffre = 0 self.photo_active = None @@ -781,14 +782,21 @@ class PB: else: # grille_4 et bande4x2 nb = 4 delai = self.cfg.get("camera",{}).get("compte_a_rebours",3) - self.photos=[]; self.gal_surfs=[]; self.gal_ready=False; self.session_qr=None; self.session_url=None + self.photos=[]; self._prev_thumbs=[]; self.gal_surfs=[]; self.gal_ready=False; self.session_qr=None; self.session_url=None + strip_bw = W // nb - 4 for i in range(nb): self.num_ph=i; self.etat=S_PREV; time.sleep(1.5) self.etat=S_COMPTE for c in range(delai,0,-1): self.chiffre=c; time.sleep(1) self.etat=S_CAP; ch=self._capturer() - if ch: self.photos.append(ch); self.compteur+=1; self._sav_cpt() + if ch: + self.photos.append(ch); self.compteur+=1; self._sav_cpt() + try: + img = ImageOps.exif_transpose(Image.open(ch).convert("RGB")).resize((strip_bw, 200), Image.LANCZOS) + self._prev_thumbs.append(pygame.image.fromstring(img.tobytes(), img.size, "RGB")) + except: + self._prev_thumbs.append(None) time.sleep(0.4) if not self.photos: self.etat=S_ACCUEIL; return self._upload_booth_bg(self.photos) # upload + QR session en arrière-plan @@ -822,15 +830,53 @@ class PB: c=cfg_load(); c.setdefault("compteur",{})["photos_prises"]=self.compteur; cfg_save(c) except: pass + def _make_composite(self, photos): + """Crée une image composite (bande ou grille) à partir des photos.""" + canvas = Image.new("RGB", (W, H), (0, 0, 0)) + if self.format in ("bande3x2", "bande4x2"): + nb = 3 if self.format == "bande3x2" else 4 + pw = W // nb + sh = (H - 150) // 2 + for i, ch in enumerate(photos[:nb]): + try: + img = ImageOps.exif_transpose(Image.open(ch).convert("RGB")).resize((pw, sh), Image.LANCZOS) + canvas.paste(img, (i * pw, 5)) + canvas.paste(img, (i * pw, sh + 10)) + except Exception as e: + print(f"composite bande: {e}") + else: # grille_4 + gw = W // 2 - 15 + gh = H // 2 - 75 + pos = [(5, 5), (W // 2 + 5, 5), (5, H // 2 - 65), (W // 2 + 5, H // 2 - 65)] + for i, ch in enumerate(photos[:4]): + try: + img = Image.open(ch).convert("RGB").resize((gw, gh), Image.LANCZOS) + canvas.paste(img, pos[i]) + except Exception as e: + print(f"composite grille: {e}") + dest = "/tmp/photobooth_composite.jpg" + canvas.save(dest, "JPEG", quality=90) + return dest + def _upload_booth(self, photos): - """Upload photos vers photobooth.copydev.fr/api/sessions/{code}/upload.""" + """Upload vers photobooth.copydev.fr/api/sessions/{code}/upload. + En mode multi, upload le composite ; en mode simple, upload la photo.""" b = self.cfg.get("booth", {}) url = b.get("url", "https://photobooth.copydev.fr").rstrip("/") code = b.get("session_code", "") if not code: return None + # Mode multi → composite unique + if self.mode == "multi" and len(photos) > 1: + try: + to_upload = [self._make_composite(photos)] + except Exception as e: + print(f"composite échoué, upload individuel: {e}") + to_upload = photos + else: + to_upload = photos from urllib.request import Request, urlopen ok = 0 - for ch in photos: + for ch in to_upload: try: boundary = "----PBUpload" with open(ch, "rb") as f: data = f.read() @@ -1019,14 +1065,53 @@ class PB: return btns # ── Preview / Compte / Capture ──────────────────────────────────────────── + def _draw_multi_strip(self): + """Overlay bas : cases multipose (done/courant/futur).""" + T = self.T + nb = 3 if self.format == "bande3x2" else 4 + STRIP_H = 200 + STRIP_Y = H - STRIP_H - 10 + BW = W // nb + # Fond semi-transparent + bg = pygame.Surface((W, STRIP_H + 20), pygame.SRCALPHA) + bg.fill((0, 0, 0, 160)) + self.scr.blit(bg, (0, STRIP_Y - 10)) + for i in range(nb): + bx = i * BW + 2 + by = STRIP_Y + bw = BW - 4 + bh = STRIP_H + if i < len(self._prev_thumbs): + th = self._prev_thumbs[i] + if th: + ts = th if th.get_size() == (bw, bh) else pygame.transform.scale(th, (bw, bh)) + self.scr.blit(ts, (bx, by)) + pygame.draw.rect(self.scr, T["prim"], (bx, by, bw, bh), 2) + tick = self.F["xs"].render("✓", True, T["prim"]) + self.scr.blit(tick, (bx + bw - tick.get_width() - 6, by + 4)) + elif i == self.num_ph: + # Case courante : cadre pulsant blanc + pulse = int(200 + 55 * math.sin(time.time() * 5)) + pygame.draw.rect(self.scr, (pulse, pulse, pulse), (bx, by, bw, bh), 3) + lbl = self.F["xs"].render(f"▶ {i + 1}", True, (255, 255, 255)) + self.scr.blit(lbl, (bx + 8, by + 6)) + else: + ph = pygame.Surface((bw, bh), pygame.SRCALPHA) + ph.fill((30, 30, 30, 140)) + self.scr.blit(ph, (bx, by)) + pygame.draw.rect(self.scr, T["texte2"], (bx, by, bw, bh), 1) + lbl = self.F["lg"].render(str(i + 1), True, T["texte2"]) + self.scr.blit(lbl, (bx + bw // 2 - lbl.get_width() // 2, by + bh // 2 - lbl.get_height() // 2)) + def draw_preview(self): self._lv() - bande=pygame.Surface((W,80),pygame.SRCALPHA); bande.fill((0,0,0,170)) - self.scr.blit(bande,(0,H-80)) - nb=len(self.photos)+1 - total=1 if self.mode=="simple" else (3 if self.format=="bande3x2" else 4) - s=self.F["sm"].render(f"Photo {nb} / {total} — Souriez !",True,(255,255,255)) - self.scr.blit(s,(W//2-s.get_width()//2,H-62)) + if self.mode == "multi": + self._draw_multi_strip() + else: + bande=pygame.Surface((W,80),pygame.SRCALPHA); bande.fill((0,0,0,170)) + self.scr.blit(bande,(0,H-80)) + s=self.F["sm"].render("Souriez !",True,(255,255,255)) + self.scr.blit(s,(W//2-s.get_width()//2,H-62)) def draw_compte(self): self._lv(); self._overlay(110) @@ -1039,9 +1124,12 @@ class PB: pygame.draw.circle(hs,(*T["prim"],a),(hr,hr),hr-i*16) self.scr.blit(hs,(W//2-hr,H//2-hr)) self._c(s,H//2-s.get_height()//2) - total=1 if self.mode=="simple" else (3 if self.format=="bande3x2" else 4) - info=self.F["sm"].render(f"Photo {self.num_ph+1} / {total}",True,T["texte2"]) - self.scr.blit(info,(W//2-info.get_width()//2,H-90)) + if self.mode == "multi": + self._draw_multi_strip() + else: + total=1 if self.mode=="simple" else (3 if self.format=="bande3x2" else 4) + info=self.F["sm"].render(f"Photo {self.num_ph+1} / {total}",True,T["texte2"]) + self.scr.blit(info,(W//2-info.get_width()//2,H-90)) def draw_capture(self): self._lv(); self._overlay(90)