diff --git a/src/printer.py b/src/printer.py index 0a76be6..7a03101 100644 --- a/src/printer.py +++ b/src/printer.py @@ -98,46 +98,37 @@ class Printer(object): return True - def print_sms(self, msg, signature="Guest") -> None: - clean_msg = str(msg) + def print_sms(self, msg, signature="",bold=False): + clean_msg = str(msg) + "\n" clean_signature = str(signature) - if len(clean_msg) > 4096 or len(clean_msg) < 3 : + if len(clean_msg) > 4096: self.app.logger.warning("Could not print message of this length: " + str(len(clean_msg))) - raise Exception("Could not print message of this length :" + str(len(clean_msg)) + ", needs to between 3 and 4096 caracters long.") + raise Exception("Could not print message of this length :" + str(len(clean_msg)) + ", needs to be below 4096 caracters long.") - if len(signature) > 256 or len(signature) < 1: + if len(signature) > 256: self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature))) - raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.") - - self.check_paper() + raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to be below 256 caracters long.") try: self.printer.open(self.usb_args); - self.printer.set(align='left', font='a', bold=False, underline=0, width=1, height=1, density=8, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False) - self.printer.set(align='left', font='a', bold=True, underline=1, width=1, height=1, density=6, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False) - self.printer.textln(clean_msg) - self.printer.textln("") - self.printer.set(align='left', font='b', bold=False, underline=1, width=1, height=1, density=9, invert=False, smooth=True, flip=False, double_width=False, double_height=False, custom_size=False) - self.printer.textln("> " + clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime())) - self.printer.textln("") - self.printer.textln("Printed by LittlePrinter ") - self.printer.textln("n07070.xyz/articles/littleprynter") - self.printer.textln("") - self.printer.cut() + self.printer.set(align='center', font='a', bold=bold) + self.printer.textln(clean_msg ) + if clean_signature: + self.printer.textln(clean_signature ) self.printer.close() except Exception as e: self.app.logger.error("Unable to print because : " + str(e)) - raise e + self.app.logger.info("Printed text") return True - def print_img(self, path, sign) -> None: + def print_img(self, path, sign="",center=True,process=False): clean_signature = str(sign) - if len(sign) > 256 or len(sign) < 1: + if len(sign) > 256: self.app.logger.warning("Could not print signature of this length: " + str(len(clean_signature))) - raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to between 3 and 256 caracters long.") + raise Exception("Could not print signature of this length :" + str(len(clean_signature)) + ", needs to be below 256 caracters long.") if not os.path.isfile(str(path)): self.app.logger.warning("File does not exist : " + str(path)) @@ -145,31 +136,61 @@ class Printer(object): else: self.app.logger.debug("Printing file from " + str(path)) - try: - self.app.logger.debug("Proccessing the image") - path = process_image(self, path) - except Exception as e: - self.app.logger.error(str(e)) - raise e + if process: + try: + self.app.logger.debug("Proccessing the image") + path = process_image(self, path) + except Exception as e: + self.app.logger.error(str(e)) + return False + else: + self.app.logger.warning("Not proccessing the image") try: - self.check_paper() self.printer.open(self.usb_args) - self.printer.image(path) - self.printer.textln("Printed by LittlePrynter ") - self.printer.textln("n07070.xyz/articles/littleprynter") - self.printer.textln(clean_signature + " @ " + strftime("%Y-%m-%d %H:%M:%S", gmtime())) - self.printer.cut() + self.printer.image(path,center=center) self.printer.close() self.app.logger.debug("Printed an image : " + str(path)) os.remove(path) - self.app.logger.debug("Removed image.") + self.app.logger.debug("Removed image : " + str(path)) + except Exception as e: + self.printer.close() + self.app.logger.error(str(e)) + return False + + self.app.logger.info("Printed a picture") + return True + + def qr(self, content): + try: + self.printer.open(self.usb_args) + self.printer.qr(content, center=True) + self.printer.close() + except Exception as e: + self.printer.close() + self.app.logger.error(str(e)) + return False + + self.app.logger.info("Printed a QR") + return True + + + + def cut(self): + try: + self.printer.open(self.usb_args) + self.printer.cut() + self.printer.close() except Exception as e: self.printer.close() self.app.logger.error(str(e)) raise e + self.app.logger.info("Did a cut") + return True + + def process_image(self, path): brightness_factor = 1.5 # Used only if image is too dark brightness_threshold = 100 # Brightness threshold (0–255) @@ -187,9 +208,9 @@ def process_image(self, path): original_img.thumbnail((max_width, max_height), Image.LANCZOS) self.app.logger.debug("Resized the image") - # Convert to grayscale for dithering - dithered_img = original_img.convert("L").convert("1") # Dithering using default method (Floyd–Steinberg) - self.app.logger.debug("Dithered the image") + # # Convert to grayscale for dithering + # dithered_img = original_img.convert("L").convert("1") # Dithering using default method (Floyd–Steinberg) + # self.app.logger.debug("Dithered the image") # Compute brightness of original image (grayscale average) grayscale = original_img.convert("L") @@ -204,9 +225,9 @@ def process_image(self, path): enhancer = ImageEnhance.Brightness(original_img) original_img = enhancer.enhance(brightness_factor) - # Reduce contrast - contrast_enhancer = ImageEnhance.Contrast(original_img) - original_img = contrast_enhancer.enhance(contrast_factor) + # # Reduce contrast + # contrast_enhancer = ImageEnhance.Contrast(original_img) + # original_img = contrast_enhancer.enhance(contrast_factor) # Final resize check if original_img.height > max_height: diff --git a/src/raspberry.py b/src/raspberry.py index 48b5cbe..246effa 100644 --- a/src/raspberry.py +++ b/src/raspberry.py @@ -1,8 +1,10 @@ from flask_socketio import SocketIO from gpiozero import Button, LED, DigitalOutputDevice -from time import sleep +from time import sleep, gmtime, strftime +from PIL import Image import io # To check if we are on a Raspberry Pi import subprocess +import os class Raspberry(object): """ @@ -65,24 +67,16 @@ class Raspberry(object): self.led = LED(self.led_gpio) self.app.logger.debug('Activated indicator LED') - + self.indicator_countdown(iters=3) self.button = Button(self.button_gpio, pull_up=True, bounce_time=0.1) self.button.when_pressed = self.on_button_pressed self.app.logger.debug('Activated button') # The "flash" is a relay-controlled device ( light bulb for example ) self.flash = DigitalOutputDevice(self.flash_gpio) + self.flash_toggle() self.app.logger.debug('Activated flash') - - def take_picture(self): - try: - subprocess.run(['fswebcam', '--no-banner', self.image_path]) - except Exception as e: - self.app.logger.error('Unable to take a picture :' + str(e)) - raise e - return True - def indicator_countdown(self,iters=10,multi=10): for i in range(iters,0,-1): self.led.on() @@ -106,17 +100,131 @@ class Raspberry(object): self.flash.off() self.app.logger.debug("Flash turned off") + def take_picture(self): + # Validate if the image path is valid + if not os.path.isdir(os.path.dirname(self.image_path)): + self.app.logger.error(f"Invalid directory for image path: {self.image_path}") + return False + try: + result = subprocess.run( + ['fswebcam', '--no-banner', '-r', '1920x1080', self.image_path], + check=True, # Will raise CalledProcessError if the command fails + stdout=subprocess.PIPE, # Capture standard output + stderr=subprocess.PIPE # Capture error output + ) + + # Optionally log the command output + self.app.logger.debug(f"Image captured successfully: {result.stdout.decode()}") + except subprocess.CalledProcessError as e: + # Log error output if available + self.app.logger.error(f"Unable to take a picture. Error: {e.stderr.decode()}") + return False + except Exception as e: + # Catch any unexpected errors + self.app.logger.error(f"Unexpected error while taking picture: {str(e)}") + return False + + # # Overlay logo + # logo_path = 'src/static/images/requin.png' # Update path as needed + # if not self.overlay_logo(self.image_path, logo_path): + # self.app.logger.warning("Picture taken but failed to overlay logo.") + + return True + + + def overlay_logo(self, image_path, logo_path, output_path=None, position='bottom_right', margin=10): + try: + image = Image.open(image_path).convert("RGBA") + logo = Image.open(logo_path).convert("RGBA") + + # Resize logo if it's too big (logo will be 30% the width of the image) + logo_ratio = 0.30 # You can change the ratio if you want the logo bigger or smaller + logo_width = int(image.width * logo_ratio) + logo_height = int(logo.height * (logo_width / logo.width)) + logo = logo.resize((logo_width, logo_height), Image.Resampling.LANCZOS) + + # Calculate position based on the chosen location + if position == 'bottom_right': + x = image.width - logo.width - margin + y = image.height - logo.height - margin + elif position == 'top_left': + x, y = margin, margin + elif position == 'top_right': + x = image.width - logo.width - margin + y = margin + elif position == 'bottom_left': + x = margin + y = image.height - logo.height - margin + else: + raise ValueError("Invalid position. Choose from 'bottom_right', 'top_left', 'top_right', or 'bottom_left'.") + + # Composite the logo onto the image + image.paste(logo, (x, y), logo) # Use logo as its own alpha mask + + # Save the result + if not output_path: + output_path = image_path # Overwrite the original image if no output path is given + image.save(output_path) + + except Exception as e: + self.app.logger.error(f"Error overlaying logo: {e}") + return False + + return True + + def crop_to_square(self, image_path, output_path=None): + try: + image = Image.open(image_path) + width, height = image.size + + # Determine shorter side + new_edge = min(width, height) + + # Calculate cropping box (centered) + left = (width - new_edge) // 2 + top = (height - new_edge) // 2 + right = left + new_edge + bottom = top + new_edge + + image = image.crop((left, top, right, bottom)) + + if not output_path: + output_path = image_path # Overwrite original + + image.save(output_path) + + except Exception as e: + self.app.logger.error(f"Error cropping image to square: {e}") + return False + + return True def on_button_pressed(self): self.app.logger.debug("Button has been pressed") - self.socketio.emit('button_pressed') # Notify clients that a button has been pressed on the raspberry pi - self.indicator_countdown(iters=5,multi=20) # The indicator will flash a countdown LED - self.flash_toggle() + self.led.on() + self.app.logger.debug("Counting down") + self.indicator_countdown(iters=4,multi=20) # The indicator will flash a countdown LED + self.app.logger.debug("Taking picture") try: + self.flash.on() self.take_picture() - self.printer.print_image(self.image_path) - self.socketio.emit('picture_taken') # Notify clients that a picture has been taken except Exception as e: self.app.logger.error("Could not take a picture after the button press : " + str(e)) + finally: + self.flash.off() + self.app.logger.debug("Printing picture") + self.led.on() + self.crop_to_square(self.image_path) + self.printer.print_img('src/static/images/extase-club.png',process=True) + self.printer.print_img(self.image_path,process=True) + self.printer.print_sms("") + self.printer.print_sms("With Love From Société.Vide",signature="",bold=True) + self.printer.print_sms("Printed by LittlePrynter",signature="") + self.printer.print_sms("n07070.xyz",signature="") + self.printer.print_sms(strftime("%Y-%m-%d %H:%M", gmtime()),signature="") + self.printer.qr("https://n07070.xyz/articles/littleprynter") + self.printer.cut() + self.led.off() + self.app.logger.debug("Done printing picture") return True \ No newline at end of file diff --git a/src/web.py b/src/web.py index 6b5e5b0..e1a57c0 100644 --- a/src/web.py +++ b/src/web.py @@ -17,6 +17,7 @@ class Web(object): self.app.logger.debug("Printing : " + str(texte) + " from " + str(sign)) try: self.printer.print_sms(texte, sign) + self.printer.cut() except Exception as e: self.app.logger.error(e) flash("Error while printing the SMS : "+ str(e)) @@ -24,13 +25,14 @@ class Web(object): flash("You message " + str( texte ) + " has been printed :)") - def print_image(self, image, sign: str): + def print_image(self, image, sign): self.app.logger.debug("Uploading file") try: self.app.logger.debug("Uploading file from " + str(sign)) if self.upload_file(image): self.app.logger.debug("File has been uploaded, printing...") - self.printer.print_img(os.path.join(self.app.config['UPLOAD_FOLDER'], secure_filename(image.filename)), sign) + self.printer.print_img(os.path.join(self.app.config['UPLOAD_FOLDER'], secure_filename(image.filename)), sign=sign,process=True) + self.printer.cut() except Exception as e: self.app.logger.error(e) flash("Could not upload file." + str(e))