From 378a9a44d595dbc544c4fef54c0b5959aaeb1e23 Mon Sep 17 00:00:00 2001 From: Jules Date: Sun, 22 Mar 2026 00:03:40 +0100 Subject: [PATCH] =?UTF-8?q?Impression=20CUPS=20r=C3=A9elle=20+=20stickers/?= =?UTF-8?q?cadres=20de=20d=C3=A9mo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Service printer : - crop_to_ratio() : recadrage auto au ratio du format - prepare_for_print() : crop + resize aux pixels exacts (300 DPI) - print_image() : envoi via lp à CUPS avec le bon nombre de copies - list_printers() : détection des imprimantes disponibles Print screen : - Impression dans un thread (pas de freeze UI) - Feedback : "Envoi à l'imprimante..." → "Impression terminée" / erreur - Gestion des erreurs CUPS Assets de démo : - 5 stickers (heart, star, smile, thumbsup, camera) - 2 cadres (gold, white) Co-Authored-By: Claude Opus 4.6 (1M context) --- assets/frames/gold_frame.png | Bin 0 -> 2506 bytes assets/frames/white_frame.png | Bin 0 -> 2454 bytes assets/goodies/camera.png | Bin 0 -> 5218 bytes assets/goodies/heart.png | Bin 0 -> 2418 bytes assets/goodies/smile.png | Bin 0 -> 5599 bytes assets/goodies/star.png | Bin 0 -> 5599 bytes assets/goodies/thumbsup.png | Bin 0 -> 2399 bytes src/services/printer.py | 162 +++++++++++++++++++++++++++++++++ src/ui/screens/print_screen.py | 43 +++++++-- 9 files changed, 197 insertions(+), 8 deletions(-) create mode 100644 assets/frames/gold_frame.png create mode 100644 assets/frames/white_frame.png create mode 100644 assets/goodies/camera.png create mode 100644 assets/goodies/heart.png create mode 100644 assets/goodies/smile.png create mode 100644 assets/goodies/star.png create mode 100644 assets/goodies/thumbsup.png create mode 100644 src/services/printer.py diff --git a/assets/frames/gold_frame.png b/assets/frames/gold_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..ba41de8b9c1da8e6e9f39da63f52d2076edc3908 GIT binary patch literal 2506 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i28yg*QRD=q6p}rHd>I(3)EF2VS{N99 z0fib~Fff!FFfhDIU|_JC!N4G1FlSew4N#&Yz$e5NNdLc1cl7`Nmgsk>K)cmSg8YIR z7&OB>cijE<@16#K+Es23PiBj+oKvT%{dEa*Ww|D0V{o2lyB1^R?UK7d9gIod?k?)O z{411!9QG1VUsv{5%%bAV(vN);9s@;wdb&7yzWqu6(P(Q^_3!k>%V+JU z{Xew+xH^A9gkN9%?|n9LE1D0NeEO9C+T!jRIZokRzs>!dKi_0dT=jYL-O8N!vvKT~ zV{^*C6jqv6ORYI@_34KG>zm6DdhL<>^6-_6-L0C-$7kQ1oZz}o@5$RYjvKV%>rU^Q zW6K`)?6daQwZFqZEM0y6-M6?ia?GoXKfe6=^Nr>PC;9m+=U44#*;iWkij(8bx9fZF zy}oIjuyma~bERc9>zXU=_uKc(-pdsB>hAp7J7?q=SC?+qIlkM@de`4|@h=Yj-+g;= zte}kk>2>i7?w^h0tuEfaxM^~#^}h#y|Gqi7!TtODrh_Kl1eDhhKlb*|?$nu=dxd-!HEG9WFEPcA~=hn8pK(ABLXzR8#UREY3`Wxd(b=F)(?))--Ar52Z|_bT zOh{jR`S;D}#P{#o8)bU^^3(HM_wJTu`&3d=Wm2vE=I)33qnjk>+TA%T$6Z}!u9NsP zx#Eph3v;nOU*gTa^Y{OoRU5py`1iMLqmKGx{+iQ|8t%MZ@gOC#ue|0-Qr`M1 zyK0U#7k<9n|GsGM**GTcl5g{WA6TE}-*|QBk1q>Ss!gkT)||QeZL@pl^v%}~oQkRY zxZ&^DHmfd`LCR$x)+vm+o|6IPS!*}yp$xrF)`uESuaR$%X^JCxq7x!d-PgQJx z_T?)FK#IZ0z{pJ3z*yJFFvQ5%%D~vl z$XwgNz{i0g4#Zd;JDd3dtTpz6=aiY77hwEes65 zfIK!C;Za=?>+ziY2QNOcgNaZ=vOl{@q79)<_$?=Lem z?3moZFoA=Ep_6vh$<3Aa%i|B|?bg|yFg^AE?l+hH&6zjV9At>eF*8zSU{GOVa!^tj zrAC8bG!=|ygwe8Kv^X5C5vWlcZc5ws?)y4Mi}bg1?bGG?53K!jh)F?EfI*0nkyg|s zeZGyNn|&O=9R)T_R7+eVN>UO_QmvAUQh^kMk%5t!u7R+oZUJsR8_hUsfqED`UHx3vIVCg! E0KsTQd;kCd literal 0 HcmV?d00001 diff --git a/assets/goodies/camera.png b/assets/goodies/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..c30dc50200aed9393e1c32e6d370c7a5f1c646a5 GIT binary patch literal 5218 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 JD!yzN{V#qE$@2gJ literal 0 HcmV?d00001 diff --git a/assets/goodies/heart.png b/assets/goodies/heart.png new file mode 100644 index 0000000000000000000000000000000000000000..14e0bcb6ac95cddeaae092606b080f718b9e1f33 GIT binary patch literal 2418 zcmb_e4N#Kl7Jh*>%0_E$ns@V$rh8W_OHwRTQ&N+kidI|vqe3$?OtXNMQ2g7q?V6>Q zWoD7Osa(jvA|kx0ZH;NAWNMlUxs`!`L5WC$$o<^CzdO4-vooEU_dD~RbDr;<^M22J z=F9n#gtIVTVGaPWAm9T-Al){y_k|wG8?1x2hK?R7>i^=#kJ%CREtij%tE$GGPgK=Tq=yw2Jr3Qn;0-z$2NZKc+KYxYLM;l{`t0e?Op>3-{gjhdD1>VomzFqHaIV@%(a z?^HJ1-J?^dN?B6=8mOM%k<7Oi!LP3ld(BWW@_TZgWG!&k~%Ca3~%nRe92~=XvQk}I9U?88%0QS zJh_VuJM%oER8<|;vS!XIRL3n>Z#n0^8`G!dxH{uvh5=x5-+fYjONONO3WEPJW z%Eu!%FJK_Ux%!nS18NM%?K)k*v#gv+P$qxdZA*8Gf4ush;jWBvKo zHk%al>*9s4x(5f@pu>0uqSE~BISBb*SFryzj>9HXz<-~7P% zjBF8@JfzZx;jQL3>TJY&KD)uC$q(ntsyE7YOvd@Acy>jbZM6 zeuhIev^VxlHOPh#iE+qJRnf2XQ}BZf78N_k`SC6Cx0n$e?w zqUic|C8_?3zxnWF-hVl|XG`(FIJ$t24Cnvcg754;uNBY$Tyk%qMDBw|ySsac-WJLT zMZ4Q9E2baTLhzJpGvj-s&FItNP};_4v)bBXIDsPB36L;gEu$Pk{6zd< z?J*8_-UV^k!MCu=fX}=j=*xZOZVZJW@+=pgK{`EJNVBxo9Ce4@m2cNCs++O}>I*+R zGfrEmt47PubT*GQn`~c_?c@{KQ0I3$7U-(3$+;Ohcjx>|-e@Em*mZLA-)qwyTO|RN zTkOSsR&8r4_p?6=nNvM$X{u5ZW0t7m+zSz2?yer%oIV}CIte7KFuNHPa-&+kC*bqV zC5m1R?TC7Id)#0H23NmA0my^_twm4q#+`u>i@ zl?DAY{Ej%cn*in9)H6lt-61IKugq=X%mdNPSg#m*ETn)N+HK=HwEH@@4PUvrd!gOE z+|X;$XfHH6q`$7<{{V?e2WSV`zYSQGZQ}v~od1)7NlT1nFryQX{x*hrcB5v#c)~VP KV6A`DH~$0<(iw~Z literal 0 HcmV?d00001 diff --git a/assets/goodies/smile.png b/assets/goodies/smile.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa64dcee76b9783960eb1874dd77a4577ae7d1d GIT binary patch literal 5599 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 C=8(n! literal 0 HcmV?d00001 diff --git a/assets/goodies/star.png b/assets/goodies/star.png new file mode 100644 index 0000000000000000000000000000000000000000..6aa64dcee76b9783960eb1874dd77a4577ae7d1d GIT binary patch literal 5599 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 C=8(n! literal 0 HcmV?d00001 diff --git a/assets/goodies/thumbsup.png b/assets/goodies/thumbsup.png new file mode 100644 index 0000000000000000000000000000000000000000..3dac677bbe11d9f6a453af7351fd1b9e4c571b9c GIT binary patch literal 2399 zcmb_e3s93+7QP{kC{$rqD6U1o`Ur1RNaYcPG~x!@2`CU=D&eJo5FYs>m_P_rsV$5M ztF3?=PytgyL2Qs1T*carVj)q2pg;oc5{Q5#AjUufWPhmJot;@bv%9@B|9|G5@1A?k zfB)~CbHCglL|kvO*#rQvJ}|&P1eTpEkNFz-z0^sQ!@@2hAcO<}b0+`=rvT8ws)FYL z9LE4K84G}S1pro=e`!D90|RT~KPCDbR>!(Xtqj%}(*sHSj3<$nR!9rzC5r~YTE9U5 zkHbz3PRk#aSShy{&kSVOzi;`;$Gm5~;r!;y@!uG4G-|TJinm&9r|#Zuz^C^YJT7Q= zjC2emq|AmflO0XM9Yem*w8Z%DNxn{y9p-EPaPqxCw)r`f%j=mu;-af_-=lVYxBJM` zsh3&rofkCwX4F)wyTdHuXntedeW9ZnCpVrp^Pmn2)WX_{7=q9lrYJXJgMIjL zAn{tqh4Iqs*wb8^5_xnp<*RZ1#aP9_BslQctVL?ryp^p_` zQ*3yQZSag~FE90F?;wc~w;PwWh6N}26mm}oa^ld7<-OjzvCJxUo=1-BrHRr*fphX% ziD;ol5!Cva*@h3vdY5&+bmtlz#io;EYA>O0PEEi1L1o_7oJ&Pgh}#m9mG;L#;>J`Q zCEOYxmb6q6IduC@E9KD(ywu~hSR!gkD1nrS!E{AQ>o%|9!PIY>cA1M1ioW3wQl=fg zRW;T99niemZZ>h-q6NsxtgO6gS^h@_NmQ|>?NC~Vctf3j^yXP#GU_pA<4O4xSr{E^ z7s#?b9ZW`?>5HndX^IdzE?b0aIhU=%l@jC(J!aXHBW<70Q*94!e6}S(%jexM9hV@@ z-LqSB_c#&RDgcH0DKak|w>^twitbipN1n^lGxJhZ{=vMPp^@^ljrl)X71SgZwGP2MRmM;YP2Fhbb*7XtGu z|215;EJGyUQs3sk#*xWneg0xCRmgw!8HIgfJOWm*7e)2#F$rO4;6Lo{TO{(ir5Q+S zY~=BR4*m62uPn+&piolu!DMn@DmP}43Qr=v81+2fWdB3mgVtzhM{Th2elI3 z)WLjw;K?kG&ExTO;#v2RPR>|X7kggAfjwU%@A^D{xyH<(IS0dYeixIUs2fA9oi}k? ze&-|j{|@kfuzsc702neajd}$BPO9$>zZ?tu^ILw8zqt}jz*dwS%l=;^6c8bpXtlyA z3JeHW82pBv5w;4Loe%o-!j0Wm%Z!9R`P)XTVpI6=R+gi z>qDdW+Sb>8c>`WdKM3ua_Ss44yXQ~E|Knli>D$g(t6ql@T3s(eG@)z)nc z{#@3`YJFsiJFOZ#OLslwD56qXg-W#U1BKn# z(Rh8VvI_~WKH&KES2@z2DRsRt!CCxsgKVLo@_s~p zR0p*9>idvk@C<8`SX~#-Xol7wdDA^F&-rmQCDOOH&y#Tbtox;Xa<9zOa#f1o=+OY| zuP`8r=$dc;#LI>69bgB2UnD&-j()^Dfp!EIzzyTJ+Xdt9;`U*fo4Ys0-P;Z0gu!@Y zFd+li%KpuOnvs}tl<{_hZKtf9VFUYLJ target_ratio: + # Image trop large → crop horizontal + new_w = int(h * target_ratio) + offset = (w - new_w) // 2 + return image.crop((offset, 0, offset + new_w, h)) + else: + # Image trop haute → crop vertical + new_h = int(w / target_ratio) + offset = (h - new_h) // 2 + return image.crop((0, offset, w, offset + new_h)) + + +def prepare_for_print(image_path, format_name, crop_rect=None): + """Prépare une image pour l'impression : crop, redimensionnement, export. + + Args: + image_path: chemin de l'image source + format_name: "10x15", "13x18", "15x20" + crop_rect: (x, y, w, h) en pixels de l'image source, ou None pour auto + + Returns: + chemin du fichier temporaire prêt à imprimer + """ + img = Image.open(image_path) + + # Appliquer le crop manuel si fourni + if crop_rect: + x, y, w, h = crop_rect + img = img.crop((x, y, x + w, y + h)) + else: + # Auto-crop au ratio + ratio = FORMAT_RATIOS.get(format_name, 1.5) + img = crop_to_ratio(img, ratio) + + # Redimensionner aux dimensions exactes du format + target = FORMAT_PIXELS.get(format_name, (1181, 1772)) + + # Orienter selon l'image + if img.width < img.height: + # Portrait + target = (min(target), max(target)) + else: + # Paysage + target = (max(target), min(target)) + + img = img.resize(target, Image.Resampling.LANCZOS) + + # Sauvegarder en JPEG haute qualité + output = os.path.join(tempfile.gettempdir(), f"photostation_print_{os.getpid()}.jpg") + img.save(output, "JPEG", quality=95, dpi=(PRINT_DPI, PRINT_DPI)) + return output + + +def print_image(image_path, format_name, printer="", copies=1, crop_rect=None): + """Prépare et imprime une image. + + Args: + image_path: chemin de l'image source + format_name: "10x15", "13x18", "15x20" + printer: nom CUPS (vide = défaut) + copies: nombre de copies + crop_rect: (x, y, w, h) ou None + + Returns: + (success, message) + """ + try: + # Préparer l'image + print_file = prepare_for_print(image_path, format_name, crop_rect) + + # Construire la commande lp + cmd = ["lp"] + if printer: + cmd.extend(["-d", printer]) + cmd.extend(["-n", str(copies)]) + # Options d'impression + cmd.extend(["-o", "fit-to-page"]) + cmd.extend(["-o", f"resolution={PRINT_DPI}dpi"]) + cmd.append(print_file) + + result = subprocess.run( + cmd, capture_output=True, text=True, timeout=30 + ) + + if result.returncode == 0: + # Extraire l'ID du job + job_info = result.stdout.strip() + print(f"[Impression] OK: {job_info}") + return True, job_info + else: + error = result.stderr.strip() or result.stdout.strip() + print(f"[Impression] Erreur: {error}") + return False, error + + except FileNotFoundError: + return False, "Commande lp non trouvée — CUPS installé ?" + except subprocess.TimeoutExpired: + return False, "Timeout impression" + except Exception as e: + return False, str(e) + finally: + # Nettoyage + try: + if 'print_file' in locals(): + os.unlink(print_file) + except OSError: + pass diff --git a/src/ui/screens/print_screen.py b/src/ui/screens/print_screen.py index 76c0b3b..43da397 100644 --- a/src/ui/screens/print_screen.py +++ b/src/ui/screens/print_screen.py @@ -321,15 +321,42 @@ class PrintScreen(QWidget): self.price_label.setStyleSheet("color: #F44336; font-weight: bold;") def _do_print(self, printer=""): - """Lance l'impression réelle.""" + """Lance l'impression réelle via CUPS.""" + from services.printer import print_image + import threading + + self.print_btn.setEnabled(False) + self.print_btn.setText("Impression en cours...") + self.price_label.setText("Envoi à l'imprimante...") + self.price_label.setStyleSheet("color: #FF9800; font-weight: bold;") + + def run_print(): + results = [] + for path, qty in self.print_list: + success, msg = print_image( + path, self.selected_format, + printer=printer, copies=qty + ) + results.append((path, qty, success, msg)) + + # Retour au thread principal + from PyQt6.QtCore import QTimer + from functools import partial + QTimer.singleShot(0, partial(self._print_done, results)) + + threading.Thread(target=run_print, daemon=True).start() + + def _print_done(self, results): + """Callback après impression.""" self.print_btn.setEnabled(True) self.print_btn.setText("IMPRIMER") - for path, qty in self.print_list: - print(f"[Impression] {path} x{qty} → {self.selected_format} cm" - f" (printer: {printer or 'défaut'})") - # TODO: appel CUPS réel + total = sum(q for _, q, _, _ in results) + errors = [msg for _, _, ok, msg in results if not ok] - total = sum(q for _, q in self.print_list) - self.price_label.setText(f"Impression lancée — {total} photo{'s' if total > 1 else ''}") - self.price_label.setStyleSheet("color: #4CAF50; font-weight: bold;") + if errors: + self.price_label.setText(f"Erreur : {errors[0]}") + self.price_label.setStyleSheet("color: #F44336; font-weight: bold;") + else: + self.price_label.setText(f"Impression terminée — {total} photo{'s' if total > 1 else ''}") + self.price_label.setStyleSheet("color: #4CAF50; font-weight: bold;")