def state_preview_enter(self, cfg, app, win): LOGGER.info("Show preview before next capture") if not app.capture_nbr: app.capture_nbr = app.capture_choices[0] if not app.capture_date: app.capture_date = time.strftime("%Y-%m-%d-%H-%M-%S") app.camera.preview(win)
def __init__(self, name='default', max_pages=-1): self._conn = cups.Connection() if cups else None self._notifier = Subscriber(self._conn) if cups else None self.name = None self.max_pages = max_pages self.nbr_printed = 0 if not cups: LOGGER.warning( "No printer found (pycups or pycups-notify not installed)") return # CUPS is not installed elif not name or name.lower() == 'default': self.name = self._conn.getDefault() if not self.name and self._conn.getPrinters(): self.name = list( self._conn.getPrinters().keys())[0] # Take first one elif name in self._conn.getPrinters(): self.name = name if not self.name: if name.lower() == 'default': LOGGER.warning( "No printer configured in CUPS (see http://localhost:631)") else: LOGGER.warning( "No printer named '%s' in CUPS (see http://localhost:631)", name) else: LOGGER.info("Connected to printer '%s'", self.name)
def pibooth_reset(cfg, hard): """Restore default template file.""" template_path = cfg.getpath('PICTURE', 'template') if template_path and (hard or not osp.isfile(template_path)): LOGGER.info("Generate picture template file in '%s'", template_path) with open(template_path, 'w') as fp: fp.write(DEFAULT)
def do_actions(self, events): if self.app.find_print_qr_event(events): # print qr code: if self.app.qr_printer.nbr_printed == 0: LOGGER.info("print qr code now") self.app.qr_printer.print_file( self.app.previous_picture_qr_file_print, 1) elif self.app.find_print_event( events) and self.app.previous_print_picture_file: with timeit("Send final picture to printer"): self.app.led_print.switch_on() print_value = self.app.config.get('SERVER', 'print_qr_code') if print_value == "Picture" or print_value == "Picture and Qr Code": self.app.printer.print_file( self.app.previous_print_picture_file, self.app.config.getint('PRINTER', 'pictures_per_page')) if print_value == "Qr Code" or print_value == "Picture and Qr Code": self.app.printer.print_file( self.app.previous_picture_qr_file, self.app.config.getint('PRINTER', 'pictures_per_page')) time.sleep(1) # Just to let the LED switched on self.app.nbr_duplicates += 1 self.app.led_print.blink() self.printed = True
def find_camera(): """Initialize the camera depending of the connected one. The priority order is chosen in order to have best rendering during preview and to take captures. The gPhoto2 camera is first (drivers most restrictive) to avoid connection concurence in case of DSLR compatible with OpenCV. """ rpi_cam_proxy = get_rpi_camera_proxy() gp_cam_proxy = get_gp_camera_proxy() cv_cam_proxy = get_cv_camera_proxy() if rpi_cam_proxy and gp_cam_proxy: LOGGER.info("Configuring hybrid camera (Picamera + gPhoto2) ...") close_proxy(None, None, cv_cam_proxy) return HybridRpiCamera(rpi_cam_proxy, gp_cam_proxy) elif cv_cam_proxy and gp_cam_proxy: LOGGER.info("Configuring hybrid camera (OpenCV + gPhoto2) ...") close_proxy(rpi_cam_proxy, None, None) return HybridCvCamera(cv_cam_proxy, gp_cam_proxy) elif gp_cam_proxy: LOGGER.info("Configuring gPhoto2 camera ...") close_proxy(rpi_cam_proxy, None, cv_cam_proxy) return GpCamera(gp_cam_proxy) elif rpi_cam_proxy: LOGGER.info("Configuring Picamera camera ...") close_proxy(None, gp_cam_proxy, cv_cam_proxy) return RpiCamera(rpi_cam_proxy) elif cv_cam_proxy: LOGGER.info("Configuring OpenCV camera ...") close_proxy(rpi_cam_proxy, gp_cam_proxy, None) return CvCamera(cv_cam_proxy) raise EnvironmentError("Neither Raspberry Pi nor GPhoto2 nor OpenCV camera detected")
def load_plugins(plugin_manager, *paths): """Return the list of core plugins and load those from the given paths. note:: by default hooks are called in LIFO registered order thus plugins register order may be important. """ plugins = [] for path in paths: plugin = load_module(path) if plugin: LOGGER.info("Plugin '%s' loaded", path) plugins.append(plugin) plugins += [ ViewPlugin(plugin_manager), # Last called PrinterPlugin(plugin_manager), PicturePlugin(plugin_manager), CameraPlugin(plugin_manager), LightsPlugin(plugin_manager) ] # First called for plugin in plugins: plugin_manager.register(plugin) # Check that each hookimpl is defined in the hookspec # except for hookimpl with kwarg ``optionalhook=True``. plugin_manager.check_pending()
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')
def regenerate_all_images(plugin_manager, config, basepath): """Regenerate the pibboth images from the raw images and the config. """ if not osp.isdir(osp.join(basepath, 'raw')): return capture_choices = config.gettuple('PICTURE', 'captures', int, 2) for captures_folder in os.listdir(osp.join(basepath, 'raw')): captures_folder_path = osp.join(basepath, 'raw', captures_folder) if not osp.isdir(captures_folder_path): continue captures = get_captures(captures_folder_path) LOGGER.info("Generating image from raws in folder %s", captures_folder_path) if len(captures) == capture_choices[0]: idx = 0 elif len(captures) == capture_choices[1]: idx = 1 else: LOGGER.warning( "Folder %s doesn't contain the correct number of pictures", captures_folder_path) continue default_factory = get_picture_factory( captures, config.get('PICTURE', 'orientation')) factory = plugin_manager.hook.pibooth_setup_picture_factory( cfg=config, opt_index=idx, factory=default_factory) picture_file = osp.join(basepath, captures_folder + "_pibooth.jpg") factory.save(picture_file)
def _on_event(self, evt): """ Call for each new printer event. """ LOGGER.info(evt.title) pygame.event.post(pygame.event.Event(PRINTER_TASKS_UPDATED, tasks=self.get_all_tasks()))
def write_exif(filename, capture_nbr, pic_id): """Adding Exif data to image files """ try: im = Image.open(filename) exif_dict = piexif.load(filename) # process im and exif_dict... w, h = im.size exif_dict["0th"][piexif.ImageIFD.XResolution] = (w, 1) exif_dict["0th"][piexif.ImageIFD.YResolution] = (h, 1) exif_dict["0th"][piexif.ImageIFD.Model] = "Fotobox vom Prinsenhof" exif_dict["0th"][piexif.ImageIFD.Make] = "Prinsenhof" exif_dict["0th"][piexif.ImageIFD.Software] = "pibooth" exif_dict["0th"][piexif.ImageIFD.ImageDescription] = "Ein Foto aus der Fotobooth vom Prinsenhof in Porta Westfalica" exif_dict["0th"][piexif.ImageIFD.DocumentName] = "Fotobox Image" exif_dict["0th"][piexif.ImageIFD.Artist] = "bpw23" exif_dict["0th"][piexif.ImageIFD.HostComputer] = "www.prinsenhof.de" exif_dict["0th"][piexif.ImageIFD.ImageNumber] = capture_nbr exif_dict["0th"][piexif.ImageIFD.Copyright] = "www.prinsenhof.de" exif_dict["Exif"][piexif.ExifIFD.ImageUniqueID] = str(pic_id) exif_dict["Exif"][piexif.ExifIFD.CameraOwnerName] = "Prinsenhof" exif_dict["GPS"][piexif.GPSIFD.GPSLatitudeRef] = "N" exif_dict["GPS"][piexif.GPSIFD.GPSLongitudeRef] = "O" #exif_dict["GPS"][piexif.ImageIFD.GPSLatitude] = () #exif_dict["GPS"][piexif.ImageIFD.GPSLongitude] = () exif_bytes = piexif.dump(exif_dict) LOGGER.info("EXIF: adding metadata to image file") im.save(filename, "jpeg", exif=exif_bytes) except Exception as e: LOGGER.warning(f"EXIF: couldn't add exif informations to picture [{e}]")
def get_camera(iso, resolution, rotation, flip, delete_internal_memory): """Initialize the camera depending of the connected one. If a gPhoto2 camera is used, try to kill any process using gPhoto2 as it may block camera access. The priority order is chosen in order to have best rendering during preview and to take captures. """ if gp_camera_connected() and rpi_camera_connected(): LOGGER.info("Configuring hybrid camera (Picamera + gPhoto2) ...") cam_class = HybridCamera pkill('*gphoto2*') elif gpomx_camera_connected(): LOGGER.info("Configuring gPhoto2 camera (preview with OMXPlayer) ...") cam_class = GpOmxCamera pkill('*gphoto2*') elif gp_camera_connected(): LOGGER.info("Configuring gPhoto2 camera ...") cam_class = GpCamera pkill('*gphoto2*') elif rpi_camera_connected(): LOGGER.info("Configuring Picamera camera ...") cam_class = RpiCamera elif cv_camera_connected(): LOGGER.info("Configuring OpenCV camera ...") cam_class = CvCamera else: raise EnvironmentError("Neither Raspberry Pi nor GPhoto2 nor OpenCV camera detected") return cam_class(iso, resolution, rotation, flip, delete_internal_memory)
def _create_or_retrieve_album(self): """Find albums created by this app to see if one matches album_title""" if self.album_name and self.album_id is None: for a in self.get_albums(True): if a["title"].lower() == self.album_name.lower(): self.album_id = a["id"] LOGGER.info("Uploading into EXISTING photo album -- '%s'", self.album_name) if self.album_id is None: # No matches, create new album create_album_body = json.dumps( {"album": { "title": self.album_name }}) # print(create_album_body) resp = self.session.post( 'https://photoslibrary.googleapis.com/v1/albums', create_album_body).json() LOGGER.debug("Server response: %s", resp) if "id" in resp: LOGGER.info("Uploading into NEW photo album -- '%s'", self.album_name) self.album_id = resp['id'] else: LOGGER.error( "Could not find or create photo album '%s'.\ Server Response: %s", self.album_name, resp) self.album_id = None
def build(self, rebuild=False): """Build the final image or doas nothing if the final image has already been built previously. :param rebuild: force re-build image :type rebuild: bool :return: PIL.Image instance :rtype: object """ if not self._final or rebuild: LOGGER.info("Use %s to create background", self.name) image = self._build_background() LOGGER.info("Use %s to concatenate images", self.name) image = self._build_matrix(image) LOGGER.info("Use %s to assemble final image", self.name) self._final = self._build_final_image(image) LOGGER.info("Use %s to draw texts", self.name) self._build_texts(self._final) if self._outlines: LOGGER.info("Use %s to outline boundary borders", self.name) self._build_outlines(self._final) return self._final
def __init__(self, filename, clear=False): ConfigParser.__init__(self) self.filename = osp.abspath(osp.expanduser(filename)) if not osp.isfile(self.filename) or clear: LOGGER.info("Generate the configuration file in '%s'", self.filename) dirname = osp.dirname(self.filename) if not osp.isdir(dirname): os.makedirs(dirname) generate_default_config(self.filename) self.read(self.filename) # Handle the language configuration, save it as a class attribute for easy access path = osp.join(osp.dirname(osp.abspath(__file__)), 'pictures') possibles = [ name for name in os.listdir(path) if osp.isdir(osp.join(path, name)) ] language = self.get('GENERAL', 'language') if language not in possibles: LOGGER.warning("Unsupported language '%s', fallback to English", language) PtbConfigParser.language = 'en' else: PtbConfigParser.language = language
def main(): """Application entry point. """ configure_logging() plugin_manager = create_plugin_manager() config = PiConfigParser("~/.config/pibooth/pibooth.cfg", plugin_manager) # Register plugins plugin_manager.load_all_plugins( config.gettuple('GENERAL', 'plugins', 'path'), config.gettuple('GENERAL', 'plugins_disabled', str)) LOGGER.info( "Installed plugins: %s", ", ".join([ plugin_manager.get_friendly_name(p) for p in plugin_manager.list_external_plugins() ])) # Update configuration with plugins ones plugin_manager.hook.pibooth_configure(cfg=config) # Initialize varibales normally done by the app picture_plugin = plugin_manager.get_plugin('pibooth-core:picture') picture_plugin.texts_vars['date'] = datetime.now() picture_plugin.texts_vars['count'] = Counters( config.join_path("counters.pickle"), taken=0, printed=0, forgotten=0, remaining_duplicates=config.getint('PRINTER', 'max_duplicates')) for path in config.gettuple('GENERAL', 'directory', 'path'): regenerate_all_images(plugin_manager, config, path)
def main(): """Application entry point. """ configure_logging() LOGGER.info("Listing all fonts available...") print_columns_words(get_available_fonts(), 3)
def capture(self, filename, effect=None): """Capture a new picture in a file. """ LOGGER.info("Arducam - Attempt Capture..") frame = self._cam.capture(encoding='jpeg') frame.as_array.tofile(filename) self._captures[filename] = None
def regenerate_all_images(config): captures_folders = config.getpath('GENERAL', 'directory') capture_choices = config.gettuple('PICTURE', 'captures', int) # Part that fetch the captures for captures_folder in os.listdir(osp.join(captures_folders, 'raw')): captures_folder_path = osp.join(captures_folders, 'raw', captures_folder) captures = get_captures(captures_folder_path) LOGGER.info("Generating image from raws in folder %s" % (captures_folder_path)) backgrounds = config.gettuple('PICTURE', 'backgrounds', ('color', 'path'), 2) if len(captures) == capture_choices[0]: background = backgrounds[0] elif len(captures) == capture_choices[1]: background = backgrounds[1] else: LOGGER.warning( "Folder %s doesn't contain the correct number of pictures" % captures_folder_path) continue overlays = config.gettuple('PICTURE', 'overlays', 'path', 2) if len(captures) == capture_choices[0]: overlay = overlays[0] else: overlay = overlays[1] texts = [ config.get('PICTURE', 'footer_text1').strip('"'), config.get('PICTURE', 'footer_text2').strip('"') ] colors = config.gettuple('PICTURE', 'text_colors', 'color', len(texts)) text_fonts = config.gettuple('PICTURE', 'text_fonts', str, len(texts)) alignments = config.gettuple('PICTURE', 'text_alignments', str, len(texts)) def _setup_maker(m): m.set_background(background) if any(elem != '' for elem in texts): for params in zip(texts, text_fonts, colors, alignments): m.add_text(*params) if config.getboolean('PICTURE', 'captures_cropping'): m.set_cropping() if overlay: m.set_overlay(overlay) if config.getboolean('GENERAL', 'debug'): m.set_outlines() maker = get_picture_maker(captures, config.get('PICTURE', 'orientation'), force_pil=True) _setup_maker(maker) previous_picture_file = osp.join(captures_folders, captures_folder + "_pibooth.jpg") maker.save(previous_picture_file)
def entry_actions(self): LOGGER.info("Start new pictures sequence") self.app.previous_picture = None self.app.previous_picture_file = None self.app.dirname = osp.join(self.app.savedir, time.strftime("%Y-%m-%d-%H-%M-%S")) os.makedirs(self.app.dirname) self.app.camera.preview(self.app.window)
def preview_countdown(self, timeout, alpha=60): """Show a countdown of `timeout` seconds on the preview. Returns when the countdown is finished. """ LOGGER.info("Arducam - preview-countdown...") while timeout > 0: time.sleep(1) timeout -= 1
def state_chosen_enter(self, cfg, app, win): LOGGER.info("Show picture choice (%s captures selected)", app.capture_nbr) win.show_choice(app.capture_choices, selected=app.capture_nbr) # Reset timeout in case of settings changed self.layout_timer.timeout = cfg.getfloat('WINDOW', 'chosen_delay') self.layout_timer.start()
def _on_event(self, event): """ Call for each new print event. """ LOGGER.info("%s - %s", event.get('pubDate', '?'), event.get('title', '?')) pygame.event.post( pygame.event.Event(PRINTER_TASKS_UPDATED, tasks=self.get_all_tasks()))
def state_print_enter(self, cfg, app, win): LOGGER.info("Display the final picture") win.show_print(app.previous_picture) win.set_print_number(len(app.printer.get_all_tasks()), not app.printer.is_ready()) # Reset timeout in case of settings changed self.print_view_timer.timeout = cfg.getfloat('PRINTER', 'printer_delay') self.print_view_timer.start()
def state_preview_enter(self, cfg, app, win): LOGGER.info("Take a new capture") if not app.capture_nbr: app.capture_nbr = app.capture_choices[0] if not app.dirname: savedir = cfg.getpath('GENERAL', 'directory') app.dirname = osp.join(savedir, "raw", time.strftime("%Y-%m-%d-%H-%M-%S")) os.makedirs(app.dirname) app.camera.preview(win)
def regenerate_all_images(config): """Regenerate the pibboth images from the raw images and the config """ captures_folders = config.getpath('GENERAL', 'directory') capture_choices = config.gettuple('PICTURE', 'captures', int, 2) backgrounds = config.gettuple('PICTURE', 'backgrounds', ('color', 'path'), 2) overlays = config.gettuple('PICTURE', 'overlays', 'path', 2) texts = [ config.get('PICTURE', 'footer_text1').strip('"'), config.get('PICTURE', 'footer_text2').strip('"') ] colors = config.gettuple('PICTURE', 'text_colors', 'color', len(texts)) text_fonts = config.gettuple('PICTURE', 'text_fonts', str, len(texts)) alignments = config.gettuple('PICTURE', 'text_alignments', str, len(texts)) # Part that fetch the captures for captures_folder in os.listdir(osp.join(captures_folders, 'raw')): captures_folder_path = osp.join(captures_folders, 'raw', captures_folder) if not osp.isdir(captures_folder_path): continue captures = get_captures(captures_folder_path) LOGGER.info("Generating image from raws in folder %s", captures_folder_path) if len(captures) == capture_choices[0]: overlay = overlays[0] background = backgrounds[0] elif len(captures) == capture_choices[1]: overlay = overlays[1] background = backgrounds[1] else: LOGGER.warning( "Folder %s doesn't contain the correct number of pictures", captures_folder_path) continue factory = get_picture_factory(captures, config.get('PICTURE', 'orientation')) factory.set_background(background) if any(elem != '' for elem in texts): for params in zip(texts, text_fonts, colors, alignments): factory.add_text(*params) if config.getboolean('PICTURE', 'captures_cropping'): factory.set_cropping() if overlay: factory.set_overlay(overlay) picture_file = osp.join(captures_folders, captures_folder + "_pibooth.jpg") factory.save(picture_file)
def main(): """Application entry point. """ parser = argparse.ArgumentParser(usage="%(prog)s [options]", description=pibooth.__doc__) parser.add_argument('--version', action='version', version=pibooth.__version__, help=u"show program's version number and exit") parser.add_argument("--config", action='store_true', help=u"edit the current configuration and exit") parser.add_argument("--translate", action='store_true', help=u"edit the GUI translations and exit") parser.add_argument("--reset", action='store_true', help=u"restore the default configuration/translations and exit") parser.add_argument("--fonts", action='store_true', help=u"display all available fonts and exit") parser.add_argument("--diagnostic", action='store_true', help=u"generate a diagnostic report for debugging and exit") parser.add_argument("--log", default=None, help=u"save logs output to the given file") group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbose", dest='logging', action='store_const', const=logging.DEBUG, help=u"report more information about operations", default=logging.INFO) group.add_argument("-q", "--quiet", dest='logging', action='store_const', const=logging.WARNING, help=u"report only errors and warnings", default=logging.INFO) options, _args = parser.parse_known_args() configure_logging(options.logging, '[ %(levelname)-8s] %(name)-18s: %(message)s', filename=options.log) config = PiConfigParser("~/.config/pibooth/pibooth.cfg", options.reset) language.init("~/.config/pibooth/translations.cfg", options.reset) if options.config: LOGGER.info("Editing the pibooth configuration...") config.edit() elif options.translate: LOGGER.info("Editing the GUI translations...") language.edit() elif options.fonts: LOGGER.info("Listing all fonts available...") print_columns_words(get_available_fonts(), 3) elif options.diagnostic: LOGGER.info("Starting diagnostic of DSLR camera...") diagnostic.main() elif not options.reset: LOGGER.info("Starting the photo booth application...") app = PiApplication(config) app.main_loop()
def main(): """Application entry point. """ parser = argparse.ArgumentParser(usage="%(prog)s [options]", description=pibooth.__doc__) parser.add_argument('--version', action='version', version=pibooth.__version__, help=u"show program's version number and exit") parser.add_argument("--config", action='store_true', help=u"edit the current configuration") parser.add_argument("--reset", action='store_true', help=u"restore the default configuration") parser.add_argument("--log", default=None, help=u"save console output to the given file") group = parser.add_mutually_exclusive_group() group.add_argument("-v", "--verbose", dest='logging', action='store_const', const=logging.DEBUG, help=u"report more information about operations", default=logging.INFO) group.add_argument("-q", "--quiet", dest='logging', action='store_const', const=logging.WARNING, help=u"report only errors and warnings", default=logging.INFO) options, _args = parser.parse_known_args() configure_logging(options.logging, '[ %(levelname)-8s] %(name)-18s: %(message)s', filename=options.log) config = PiConfigParser("~/.config/pibooth/pibooth.cfg", options.reset) if options.config: LOGGER.info("Editing the photo booth configuration...") config.open_editor() elif not options.reset: LOGGER.info("Starting the photo booth application...") app = PiApplication(config) app.main_loop()
def pibooth_reset(cfg, hard): """Populate sounds folder if it doesn't exists""" sound_path = cfg.getpath('SOUNDS', 'sounds_path') source_sound_path = osp.join(osp.dirname(osp.abspath(__file__)), 'sounds') if hard and osp.isdir(sound_path): shutil.rmtree(sound_path, ignore_errors=True) if not osp.isdir(sound_path): source_sound_path = osp.join(osp.dirname(osp.abspath(__file__)), 'sounds') LOGGER.info("Generate sounds directory in '%s'", sound_path) shutil.copytree(source_sound_path, sound_path)
def state_processing_exit(app, cfg): """Upload picture to Nextcloud album""" name = app.previous_picture_file rep_photos_nextcloud = app.nextcloud.rep_photos_nextcloud nextcloud_name = app.nextcloud.album_name activate_state = app.nextcloud.activate_state LOGGER.info("Upload Photo (%s)...", name) app.nextcloud.upload_photos( name, app.nextcloud.rep_photos_nextcloud + nextcloud_name + '/' + os.path.basename(name), activate_state)
def entry_actions(self): LOGGER.info("Start new pictures sequence") self.app.nbr_printed = 0 self.app.previous_picture = None self.app.previous_picture_file = None self.app.dirname = osp.join(self.app.savedir, "raw", time.strftime("%Y-%m-%d-%H-%M-%S")) os.makedirs(self.app.dirname) self.app.led_preview.switch_on() self.count = 0 self.app.window.set_picture_number(self.count, self.app.nbr_captures) self.app.camera.preview(self.app.window)