def __init__(self, iso=200, resolution=(1920, 1080), rotation=0, flip=False, delete_internal_memory=False): BaseCamera.__init__(self, resolution, delete_internal_memory) self._preview_hflip = False self._capture_hflip = flip self._rotation = rotation self._iso = iso self._overlay_alpha = 255 self._cam = cv2.VideoCapture(0) self.preview_resolution = (self._cam.get(cv2.CAP_PROP_FRAME_WIDTH), self._cam.get(cv2.CAP_PROP_FRAME_HEIGHT)) LOGGER.debug("Preview resolution is %s", self.preview_resolution) self._cam.set(cv2.CAP_PROP_ISO_SPEED, self._iso)
def gp_set_config_value(config, section, option, value): """Set camera configuration. This method don't send the updated configuration to the camera (avoid connection flooding if several values have to be changed) """ try: LOGGER.debug('Setting option %s/%s=%s', section, option, value) child = config.get_child_by_name(section).get_child_by_name(option) choices = [c for c in child.get_choices()] if not choices or value in choices: child.set_value(str(value)) else: LOGGER.warning( "Invalid value '%s' for option %s (possible choices: %s)", value, option, choices) except gp.GPhoto2Error: raise ValueError('Unsupported setting {}/{}={}'.format( section, option, value))
def get_translated_text(key): """Return the text corresponding to the key in the language defined in the config. :param key: key in the translation file :type key: str """ if not getattr(PARSER, 'filename', None): raise EnvironmentError("Translation system is not initialized") if PARSER.has_section(CURRENT) and PARSER.has_option(CURRENT, key): return PARSER.get(CURRENT, key).strip('"') elif PARSER.has_option('en', key): LOGGER.warning("Unsupported language '%s', fallback to English", CURRENT) return PARSER.get('en', key).strip('"') LOGGER.debug("No translation defined for '%s/%s' key", CURRENT, key) return None
def _on_button_printer_held(self): """Called when the printer button is pressed. """ if all(self.buttons.value): # Printer was held while capture was pressed # but don't do anything here, let capture_held handle it instead pass else: # Printer was held but capture not pressed if self._menu and self._menu.is_shown(): # Convert HW button events to keyboard events for menu event = self._menu.create_click_event() LOGGER.debug("BUTTONDOWN: generate MENU-APPLY event") else: event = pygame.event.Event(BUTTONDOWN, capture=0, printer=1, button=self.buttons.printer) LOGGER.debug("BUTTONDOWN: generate PRINTER event") pygame.event.post(event)
def pibooth_startup(app, cfg): """Create the GooglePhotosUpload instance.""" app.previous_picture_url = None client_id_file = cfg.getpath('GOOGLE', 'client_id_file') if not client_id_file: LOGGER.debug( "No credentials file defined in [GOOGLE][client_id_file], upload deactivated" ) elif not os.path.exists(client_id_file): LOGGER.error( "No such file [GOOGLE][client_id_file]='%s', please check config", client_id_file) elif client_id_file and os.path.getsize(client_id_file) == 0: LOGGER.error( "Empty file [GOOGLE][client_id_file]='%s', please check config", client_id_file) else: app.google_photos = GooglePhotosApi(client_id_file)
def _update_foreground(self, pil_image, pos=CENTER, resize=True): """Show a PIL image on the foreground. Only one is bufferized to avoid memory leak. """ image_name = id(pil_image) if pos == self.FULLSCREEN: image_size_max = (self.surface.get_size()[0] * 0.9, self.surface.get_size()[1] * 0.9) else: image_size_max = (self.surface.get_size()[0] * 0.48, self.surface.get_size()[1]) buff_size, buff_image = self._buffered_images.get( image_name, (None, None)) if buff_image and image_size_max == buff_size: image = buff_image else: if resize: image = pil_image.resize( sizing.new_size_keep_aspect_ratio(pil_image.size, image_size_max), Image.ANTIALIAS) else: image = pil_image image = pygame.image.frombuffer(image.tobytes(), image.size, image.mode) if self._current_foreground: self._buffered_images.pop(id(self._current_foreground[0]), None) LOGGER.debug("Add to buffer the image '%s'", image_name) self._buffered_images[image_name] = (image_size_max, image) self._current_foreground = (pil_image, pos, resize) if self.debug and resize: # Build rectangle around picture area for debuging purpose outlines = pygame.Surface(image_size_max, pygame.SRCALPHA, 32) pygame.draw.rect(outlines, pygame.Color(255, 0, 0), outlines.get_rect(), 2) self.surface.blit(outlines, self._pos_map[pos](outlines)) return self.surface.blit(image, self._pos_map[pos](image))
def init(filename, clear=False): """Initialize the translation system. :param filename: path to the translations file :type filename: str :param clear: restore default translations :type clear: bool """ PARSER.filename = osp.abspath(osp.expanduser(filename)) if not osp.isfile(PARSER.filename) or clear: LOGGER.info("Generate the translation file in '%s'", PARSER.filename) dirname = osp.dirname(PARSER.filename) if not osp.isdir(dirname): os.makedirs(dirname) with io.open(PARSER.filename, 'w', encoding="utf-8") as fp: for section, options in DEFAULT.items(): fp.write("[{}]\n".format(section)) for name, value in options.items(): value = value.splitlines() fp.write("{} = {}\n".format(name, value[0])) if len(value) > 1: for part in value[1:]: fp.write(" {}\n".format(part)) fp.write("\n\n") PARSER.read(PARSER.filename, encoding='utf-8') # Complete with missing language changed = False for section, options in DEFAULT.items(): if not PARSER.has_section(section): changed = True LOGGER.debug("Add [%s] to available language list", section) PARSER.add_section(section) for option, value in options.items(): PARSER.set(section, option, value) if changed: with io.open(PARSER.filename, 'w', encoding="utf-8") as fp: PARSER.write(fp)
def load_all_plugins(self, paths, disabled=None): """Register the core plugins, load plugins from setuptools entry points and the load given module/package paths. note:: by default hooks are called in LIFO registered order thus plugins register order is important. :param paths: list of Python module/package paths to load :type paths: list :param disabled: list of plugins name to be disabled after loaded :type disabled: list """ # Load plugins declared by setuptools entry points self.load_setuptools_entrypoints(hookspecs.hookspec.project_name) plugins = [] for path in paths: plugin = load_module(path) if plugin: LOGGER.debug("Plugin found at '%s'", path) plugins.append(plugin) plugins += [ LightsPlugin(self), # Last called ViewPlugin(self), PrinterPlugin(self), PicturePlugin(self), CameraPlugin(self) ] # First called for plugin in plugins: self.register(plugin) # Check that each hookimpl is defined in the hookspec # except for hookimpl with kwarg 'optionalhook=True'. self.check_pending() # Disable unwanted plugins if disabled: for name in disabled: self.unregister(name=name)
def _get_authorized_session(self): """Check if credentials file exists""" # check the default path of save credentials to allow keep None if self.google_credentials is None: self.google_credentials = os.path.join( os.path.dirname(self.client_id_file) + "/google_credentials.dat") # if first instance of application: if not os.path.exists(self.google_credentials) or \ os.path.getsize(self.google_credentials) == 0: self._auth() self.session = AuthorizedSession(self.credentials) LOGGER.debug("First run of application create credentials file %s", self.google_credentials) try: self._save_cred() except OSError as err: LOGGER.debug("Could not save auth tokens - %s", err) else: try: self.credentials = Credentials.from_authorized_user_file( self.google_credentials, self.scopes) self.session = AuthorizedSession(self.credentials) except ValueError: LOGGER.debug("Error loading auth tokens - Incorrect format")
def get_gp_camera_proxy(port=None): """Return camera proxy if a gPhoto2 compatible camera is found else return None. .. note:: try to kill any process using gPhoto2 as it may block camera access. :param port: look on given port number :type port: str """ if not gp: return None # gPhoto2 is not installed pkill('*gphoto2*') if hasattr(gp, 'gp_camera_autodetect'): # gPhoto2 version 2.5+ cameras = gp.check_result(gp.gp_camera_autodetect()) else: port_info_list = gp.PortInfoList() port_info_list.load() abilities_list = gp.CameraAbilitiesList() abilities_list.load() cameras = abilities_list.detect(port_info_list) if cameras: LOGGER.debug("Found gPhoto2 cameras on ports: '%s'", "' / '".join([p for _, p in cameras])) # Initialize first camera proxy and return it camera = gp.Camera() if port is not None: port_info_list = gp.PortInfoList() port_info_list.load() idx = port_info_list.lookup_path(port) camera.set_port_info(port_info_list[idx]) try: camera.init() return camera except gp.GPhoto2Error as ex: LOGGER.warning("Could not connect gPhoto2 camera: %s", ex) return None
def set_config_value(self, section, option, value): """Set camera configuration. This method don't send the updated configuration to the camera (avoid connection flooding if several values have to be changed) """ try: LOGGER.debug('Setting option %s/%s=%s', section, option, value) config = self._cam.get_config() child = config.get_child_by_name(section).get_child_by_name(option) if child.get_type() == gp.GP_WIDGET_RADIO: choices = [c for c in child.get_choices()] else: choices = None data_type = type(child.get_value()) value = data_type(value) # Cast value if choices and value not in choices: LOGGER.warning( "Invalid value '%s' for option %s (possible choices: %s), trying to set it anyway", value, option, choices) child.set_value(value) self._cam.set_config(config) except gp.GPhoto2Error as ex: LOGGER.error('Unsupported option %s/%s=%s (%s), configure your DSLR manually', section, option, value, ex)
def _post_process_capture(self, capture_path): """Rework and return a Image object from file. """ gp_path, effect = self._captures[capture_path] camera_file = self._cam.file_get(gp_path.folder, gp_path.name, gp.GP_FILE_TYPE_NORMAL) if self.delete_internal_memory: LOGGER.debug("Delete capture '%s' from internal memory", gp_path.name) self._cam.file_delete(gp_path.folder, gp_path.name) image = Image.open(io.BytesIO(camera_file.get_data_and_size())) # Crop to keep aspect ratio of the resolution image = image.crop(sizing.new_size_by_croping_ratio(image.size, self.resolution)) # Resize to fit the resolution image = image.resize(sizing.new_size_keep_aspect_ratio(image.size, self.resolution, 'outer')) if self._capture_hflip: image = image.transpose(Image.FLIP_LEFT_RIGHT) if effect != 'none': image = image.filter(getattr(ImageFilter, effect.upper())) image.save(capture_path) return image
def _on_button_capture_held(self): """Called when the capture button is pressed. """ if all(self.buttons.value): self.buttons.capture.hold_repeat = True if self._multipress_timer.elapsed() == 0: self._multipress_timer.start() if self._multipress_timer.is_timeout(): # Capture was held while printer was pressed if self._menu and self._menu.is_shown(): # Convert HW button events to keyboard events for menu event = self._menu.create_back_event() LOGGER.debug("BUTTONDOWN: generate MENU-ESC event") else: event = pygame.event.Event(BUTTONDOWN, capture=1, printer=1, button=self.buttons) LOGGER.debug("BUTTONDOWN: generate DOUBLE buttons event") self.buttons.capture.hold_repeat = False self._multipress_timer.reset() pygame.event.post(event) else: # Capture was held but printer not pressed if self._menu and self._menu.is_shown(): # Convert HW button events to keyboard events for menu event = self._menu.create_next_event() LOGGER.debug("BUTTONDOWN: generate MENU-NEXT event") else: event = pygame.event.Event(BUTTONDOWN, capture=1, printer=0, button=self.buttons.capture) LOGGER.debug("BUTTONDOWN: generate CAPTURE button event") self.buttons.capture.hold_repeat = False self._multipress_timer.reset() pygame.event.post(event)
def set_state(self, state_name): """Change state machine's active state """ try: # Perform any exit actions of the current state if self.active_state is not None: hook = getattr(self.pm.hook, 'state_{}_exit'.format(self.active_state)) hook(cfg=self.cfg, app=self.app, win=self.win) except Exception as ex: if self.failsafe_state and self.active_state != self.failsafe_state: LOGGER.error(str(ex)) LOGGER.debug('Back to failsafe state due to error:', exc_info=True) state_name = self.failsafe_state else: raise if state_name not in self.states: raise ValueError( '"{}" not in registered states...'.format(state_name)) # Switch to the new state and perform its entry actions LOGGER.debug("Activate state '%s'", state_name) self.active_state = state_name try: hook = getattr(self.pm.hook, 'state_{}_enter'.format(self.active_state)) hook(cfg=self.cfg, app=self.app, win=self.win) except Exception as ex: if self.failsafe_state and self.active_state != self.failsafe_state: LOGGER.error(str(ex)) LOGGER.debug('Back to failsafe state due to error:', exc_info=True) self.set_state(self.failsafe_state) else: raise
def _on_receive_signal(self, pin, frame, callback, bouncetime): last = self._last_signal_time.setdefault(pin, 0) if abs(time.time() - last) * 1000 >= bouncetime: LOGGER.debug('GPIO Mock: pin %s triggered', pin) self._last_signal_time[pin] = time.time() callback(pin)
def on_button_down(self, pin): """Post a pygame event when the button is pressed. """ LOGGER.debug('Hardware button (pin %s) triggered', pin) event = pygame.event.Event(BUTTON_DOWN, pin=pin) pygame.event.post(event)
def setmode(self, mode): LOGGER.debug("GPIO Mock: set mode %s", mode)
def setup(self, pin, direction, **kwargs): LOGGER.debug("GPIO Mock: setup pin %s to %s", pin, direction)
def upload(self, filename, album_name): """Upload a photo file to the given Google Photos album. :param filename: photo file full path :type filename: str :param album_name: name of albums to upload :type album_name: str :returns: URL of the uploaded photo :rtype: str """ photo_url = None if not self.is_reachable(): LOGGER.error( "Google Photos upload failure: no internet connexion!") return photo_url if not self._credentials: # Plugin was disabled at startup but activated after self._session = self._get_authorized_session() album_id = self.get_album_id(album_name) if not album_id: album_id = self.create_album(album_name) if not album_id: LOGGER.error("Google Photos upload failure: album '%s' not found!", album_name) return photo_url self._session.headers["Content-type"] = "application/octet-stream" self._session.headers["X-Goog-Upload-Protocol"] = "raw" with open(filename, mode='rb') as fp: data = fp.read() self._session.headers["X-Goog-Upload-File-Name"] = os.path.basename( filename) LOGGER.info("Uploading picture '%s' to Google Photos", filename) upload_token = self._session.post(self.URL + '/uploads', data) if upload_token.status_code == 200 and upload_token.content: create_body = json.dumps({ "albumId": album_id, "newMediaItems": [{ "description": "", "simpleMediaItem": { "uploadToken": upload_token.content.decode() } }] }) resp = self._session.post(self.URL + '/mediaItems:batchCreate', create_body).json() LOGGER.debug("Google Photos server response: %s", resp) if "newMediaItemResults" in resp: status = resp["newMediaItemResults"][0]["status"] if status.get("code") and (status.get("code") > 0): LOGGER.error( "Google Photos upload failure: can not add '%s' to library: %s", os.path.basename(filename), status["message"]) else: photo_url = resp["newMediaItemResults"][0][ 'mediaItem'].get('productUrl') LOGGER.info( "Google Photos upload successful: '%s' added to album '%s'", os.path.basename(filename), album_name) else: LOGGER.error( "Google Photos upload failure: can not add '%s' to library", os.path.basename(filename)) elif upload_token.status_code != 200: LOGGER.error( "Google Photos upload failure: can not connect to '%s' (HTTP error %s)", self.URL, upload_token.status_code) else: LOGGER.error( "Google Photos upload failure: no response content from server '%s'", self.URL) try: del self._session.headers["Content-type"] del self._session.headers["X-Goog-Upload-Protocol"] del self._session.headers["X-Goog-Upload-File-Name"] except KeyError: pass return photo_url
def upload_photos(self, photo_file_list, album_name, activate): """Funtion use to upload list of photos to google album :param photo_file_list: list of photos name with full path :type photo_file_list: file :param album_name: name of albums to upload :type album_name: str :param activate: use to disable the upload :type activate: bool """ self.activate = activate # interrupt upload no internet if not self._is_internet(): LOGGER.error("Interrupt upload no internet connexion!!!!") return # if plugin is disable if not self.activate: return # plugin is disable at startup but activate after so check credential file elif not self.credentials: self._get_authorized_session() self.album_name = album_name self._create_or_retrieve_album() # interrupt upload if no album id can't read or create if self.album_name and not self.album_id: LOGGER.error("Interrupt upload album not found!!!!") return self.session.headers["Content-type"] = "application/octet-stream" self.session.headers["X-Goog-Upload-Protocol"] = "raw" for photo_file_name in photo_file_list: try: photo_file = open(photo_file_name, mode='rb') photo_bytes = photo_file.read() except OSError as err: LOGGER.error("Could not read file '%s' -- %s", photo_file_name, err) continue self.session.headers["X-Goog-Upload-File-Name"] = os.path.basename( photo_file_name) LOGGER.info("Uploading photo -- '%s'", photo_file_name) upload_token = self.session.post( 'https://photoslibrary.googleapis.com/v1/uploads', photo_bytes) if (upload_token.status_code == 200) and upload_token.content: create_body = json.dumps( { "albumId": self.album_id, "newMediaItems": [{ "description": "", "simpleMediaItem": { "uploadToken": upload_token.content.decode() } }] }, indent=4) resp = self.session.post( 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate', create_body).json() LOGGER.debug("Server response: %s", resp) if "newMediaItemResults" in resp: status = resp["newMediaItemResults"][0]["status"] if status.get("code") and (status.get("code") > 0): LOGGER.error("Could not add '%s' to library -- %s", os.path.basename(photo_file_name), status["message"]) else: LOGGER.info("Added '%s' to library and album '%s' ", os.path.basename(photo_file_name), album_name) else: LOGGER.error( "Could not add '%s' to library. Server Response -- %s", os.path.basename(photo_file_name), resp) else: LOGGER.error("Could not upload '%s'. Server Response -- %s", os.path.basename(photo_file_name), upload_token) try: del self.session.headers["Content-type"] del self.session.headers["X-Goog-Upload-Protocol"] del self.session.headers["X-Goog-Upload-File-Name"] except KeyError: pass
def cleanup(self): LOGGER.debug("GPIO Mock: quit")