From 595167d62e7e286dc3671a9c45fb0b3d3ad9fdda Mon Sep 17 00:00:00 2001 From: Balthasar Reuter Date: Wed, 17 Jun 2015 01:45:12 +0200 Subject: [PATCH] Started refactoring into multiple files --- camera.py | 35 ++++++++++ gui.py | 200 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 camera.py create mode 100644 gui.py diff --git a/camera.py b/camera.py new file mode 100644 index 0000000..d5eefab --- /dev/null +++ b/camera.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# Created by br@re-web.eu, 2015 + +import subprocess + +class CameraException(Exception): + """Custom exception class to handle camera class errors""" + pass + +class Camera_gPhoto: + """Camera class providing functionality to take pictures using gPhoto 2""" + + def __init__(self): + # Print the abilities of the connected camera + print(self.call_gphoto("-a", "/dev/null")) + + def call_gphoto(self, action, filename): + # Try to run the command + try: + cmd = "gphoto2 --force-overwrite --quiet " + action + " --filename " + filename + output = subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) + if "ERROR" in output: + raise subprocess.CalledProcessError(returncode=0, cmd=cmd, output=output) + except subprocess.CalledProcessError as e: + if "Canon EOS Capture failed: 2019" in e.output: + raise CameraException("Can't focus! Move and try again!") + elif "No camera found" in e.output: + raise CameraException("No (supported) camera detected!") + else: + raise CameraException("Unknown error!\n" + '\n'.join(e.output.split('\n')[1:3])) + return output + + def take_picture(self, filename="/tmp/picture.jpg"): + self.call_gphoto("--capture-image-and-download", filename) + return filename diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..eb1894e --- /dev/null +++ b/gui.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python +# Created by br@re-web.eu, 2015 + +# TODO: +# - base everything on surfaces to allow stacking +# - incorporate render_textrect +# - restructure mainloop + +import pygame + +try: + import pygame.fastevent as EventModule +except ImportError: + import pygame.event as EventModule + + +class TextRectException: + def __init__(self, message = None): + self.message = message + def __str__(self): + return self.message + +def render_textrect(string, font, rect, text_color, background_color, justification=0, valign=0): + """Returns a surface containing the passed text string, reformatted + to fit within the given rect, word-wrapping as necessary. The text + will be anti-aliased. + + Source: http://www.pygame.org/pcr/text_rect/index.php + + Takes the following arguments: + + string - the text you wish to render. \n begins a new line. + font - a Font object + rect - a rectstyle giving the size of the surface requested. + text_color - a three-byte tuple of the rgb value of the + text color. ex (0, 0, 0) = BLACK + background_color - a three-byte tuple of the rgb value of the surface. + justification - 0 (default) left-justified + 1 horizontally centered + 2 right-justified + valign - 0 (default) top aligned + 1 vertically centered + 2 bottom aligned + + Returns the following values: + + Success - a surface object with the text rendered onto it. + Failure - raises a TextRectException if the text won't fit onto the surface. + """ + + final_lines = [] + + requested_lines = string.splitlines() + + # Create a series of lines that will fit on the provided + # rectangle. + + accumulated_height = 0 + for requested_line in requested_lines: + if font.size(requested_line)[0] > rect.width: + words = requested_line.split(' ') + # if any of our words are too long to fit, return. + for word in words: + if font.size(word)[0] >= rect.width: + raise TextRectException, "The word " + word + " is too long to fit in the rect passed." + # Start a new line + accumulated_line = "" + for word in words: + test_line = accumulated_line + word + " " + # Build the line while the words fit. + if font.size(test_line)[0] < rect.width: + accumulated_line = test_line + else: + accumulated_height += font.size(test_line)[1] + final_lines.append(accumulated_line) + accumulated_line = word + " " + accumulated_height += font.size(accumulated_line)[1] + final_lines.append(accumulated_line) + else: + accumulated_height += font.size(requested_line)[1] + final_lines.append(requested_line) + + # Check height of the text and align vertically + + if accumulated_height >= rect.height: + raise TextRectException, "Once word-wrapped, the text string was too tall to fit in the rect." + + if valign == 0: + voffset = 0 + elif valign == 1: + voffset = int((rect.height - accumulated_height) / 2) + elif valign == 2: + voffset = rect.height - accumulated_height + else: + raise TextRectException, "Invalid valign argument: " + str(valign) + + # Let's try to write the text out on the surface. + + surface = pygame.Surface(rect.size) + surface.fill(background_color) + + accumulated_height = 0 + for line in final_lines: + if line != "": + tempsurface = font.render(line, 1, text_color) + if justification == 0: + surface.blit(tempsurface, (0, voffset + accumulated_height)) + elif justification == 1: + surface.blit(tempsurface, ((rect.width - tempsurface.get_width()) / 2, voffset + accumulated_height)) + elif justification == 2: + surface.blit(tempsurface, (rect.width - tempsurface.get_width(), voffset + accumulated_height)) + else: + raise TextRectException, "Invalid justification argument: " + str(justification) + accumulated_height += font.size(line)[1] + + return surface + + +class GUI_PyGame: + """A GUI class using PyGame""" + + def __init__(self, name, size): + # Call init routines + pygame.init() + if hasattr(EventModule, 'init'): + EventModule.init() + + # Window name + pygame.display.set_caption(name) + + # Hide mouse cursor + pygame.mouse.set_cursor(*pygame.cursors.load_xbm('transparent.xbm','transparent.msk')) + + # Store screen and size + self.size = size + self.screen = pygame.display.set_mode(self.size, pygame.FULLSCREEN) + + # Clear screen + self.clear() + + def clear(self, color=(0,0,0)): + self.screen.fill(color) + + def apply(self): + pygame.display.update() + + def get_size(self): + return self.size + + def trigger_event(self, event_id, event_channel): + EventModule.post(EventModule.Event(event_id, channel=event_channel)) + + def show_picture(self, filename, size=(0,0), offset=(0,0)): + # Use window size if none given + if size == (0,0): + size = self.size + # Load image from file + image = pygame.image.load(filename) + # Extract image size and determine scaling + image_size = image.get_rect().size + image_scale = min([min(a,b)/b for a,b in zip(size, image_size)]) + # New image size + new_size = [int(a*image_scale) for a in image_size] + # Update offset + offset = tuple(a+int((b-c)/2) for a,b,c in zip(offset, size, new_size)) + # Apply scaling and display picture + image = pygame.transform.scale(image, new_size).convert() + self.screen.blit(image, offset) + + def show_message(self, msg, color=(245,245,245), bg=(0,0,0)): + # Choose font + font = pygame.font.Font(None, 144) + # Create rectangle for text + rect = pygame.Rect((0, 0, self.size[0], self.size[1])) + # Render text + text = render_textrect(msg, font, rect, color, bg, 1, 1) + self.screen.blit(text, rect.topleft) + + def mainloop(self, filename, handle_keypress, handle_mousebutton, handle_gpio_event): + while True: + # Ignore all input that happened before entering the loop + EventModule.get() + # Clear display + self.clear() + # Show idle-picture and message + if filename != None: + self.show_picture(filename) + self.show_message("Hit the button!") + # Render everything + self.apply() + # Wait for event + event = EventModule.wait() + # Handle the event + if event.type == pygame.QUIT: return + elif event.type == pygame.KEYDOWN: handle_keypress(event.key) + elif event.type == pygame.MOUSEBUTTONUP: handle_mousebutton(event.button, event.pos) + elif event.type == gpio_pygame_event: handle_gpio_event(event.channel) + + def teardown(self): + pygame.quit()