Multipose: composite upload + strip preview layout
- _make_composite(): crée une image bande (2 rangées) ou grille 2x2 à partir des photos prises, sauvée en /tmp/photobooth_composite.jpg - _upload_booth(): upload le composite unique en mode multi au lieu des photos individuelles - _draw_multi_strip(): overlay bas avec les cases numérotées — thumbnails pour les prises faites, cadre pulsant pour la case courante, placeholder gris pour les suivantes - draw_preview() / draw_compte(): utilisent _draw_multi_strip() en mode multi Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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,13 +1065,52 @@ 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()
|
||||
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))
|
||||
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))
|
||||
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):
|
||||
@@ -1039,6 +1124,9 @@ 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)
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user