Example #1
0
class StatePrint(State):
    def __init__(self):
        State.__init__(self, 'print', 'finish')
        self.timer = PoolingTimer(
            self.app.config.getfloat('PRINTER', 'printer_delay'))
        self.printed = False

    def entry_actions(self):
        self.printed = False
        if self.timer.timeout == 0:
            return  # Don't show print state

        with timeit("Display the merged picture"):
            self.app.window.show_print(self.app.previous_picture)
        self.app.led_print.blink()
        self.timer.start()

    def do_actions(self, events):
        if self.timer.timeout == 0:
            return  # Don't show print state

        if self.app.find_print_event(
                events) and self.app.previous_picture_file:
            with timeit("Send final picture to printer"):
                self.app.led_print.switch_on()
                self.app.printer.print_file(self.app.previous_picture_file)
            time.sleep(1)
            self.app.led_print.blink()
            self.printed = True

    def validate_transition(self, events):
        if self.timer.is_timeout() or self.printed:
            return self.next_name
Example #2
0
class StatePrint(State):
    def __init__(self):
        State.__init__(self, 'print')
        self.timer = PoolingTimer(
            self.app.config.getfloat('PRINTER', 'printer_delay'))
        self.printed = False

    def entry_actions(self):
        self.printed = False

        with timeit("Display the final picture"):
            self.app.window.set_print_number(
                len(self.app.printer.get_all_tasks()),
                self.app.printer_unavailable)
            self.app.window.show_print(
                self.app.previous_picture,
                self.app.config.getboolean('SERVER', 'show_qr_on_screen'),
                self.app.previous_picture_qr_file_inverted)

        self.app.led_print.blink()
        # Reset timeout in case of settings changed
        self.timer.timeout = self.app.config.getfloat('PRINTER',
                                                      'printer_delay')
        self.timer.start()

    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 validate_transition(self, events):
        if self.timer.is_timeout() or self.printed:
            if self.printed:
                self.app.window.set_print_number(
                    len(self.app.printer.get_all_tasks()),
                    self.app.printer_unavailable)
            return 'finish'
Example #3
0
class StatePrint(State):

    def __init__(self):
        State.__init__(self, 'print')
        self.timer = PoolingTimer(self.app.config.getfloat('PRINTER', 'printer_delay'))
        self.printed = False

    def entry_actions(self):
        self.printed = False
        with timeit("Display the merged picture"):
            self.app.window.show_print(self.app.previous_picture)
        self.app.led_print.blink()
        self.timer.start()

    def do_actions(self, events):
        if self.app.find_print_event(events) and self.app.previous_picture_file:

            with timeit("Send final picture to printer"):
                self.app.led_print.switch_on()
                self.app.printer.print_file(self.app.previous_picture_file,
                                            self.app.config.getint('PRINTER', 'nbr_copies'))

            time.sleep(2)  # Just to let the LED switched on
            self.app.nbr_printed += 1
            self.app.led_print.blink()
            self.printed = True

    def validate_transition(self, events):
        if self.timer.is_timeout() or self.printed:
            return 'finish'
Example #4
0
class StateFinish(State):
    def __init__(self, timeout):
        State.__init__(self, 'finish')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        self.app.window.show_finished()
        self.timer.start()

    def validate_transition(self, events):
        if self.timer.is_timeout():
            return 'wait'
Example #5
0
class StateFailSafe(State):
    def __init__(self, timeout):
        State.__init__(self, 'failsafe')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        self.app.dirname = None
        self.app.nbr_captures = None
        self.app.nbr_printed = 0
        self.app.camera.drop_captures()  # Flush previous captures
        self.app.window.show_oops()
        self.timer.start()

    def validate_transition(self, events):
        if self.timer.is_timeout():
            return 'wait'
Example #6
0
class StateChosen(State):
    def __init__(self, timeout):
        State.__init__(self, 'chosen', 'capture')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        with timeit("Set {} picture(s) mode".format(self.app.max_captures)):
            self.app.window.show_choice(self.app.max_captures)
        self.timer.start()

    def exit_actions(self):
        self.app.led_picture.switch_off()
        self.app.led_print.switch_off()

    def validate_transition(self, events):
        if self.timer.is_timeout():
            return self.next_name
Example #7
0
class StateChosen(State):

    def __init__(self, timeout):
        State.__init__(self, 'chosen')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        with timeit("Show picture choice ({} pictures selected)".format(self.app.nbr_captures)):
            self.app.window.show_choice(self.app.capture_choices, selected=self.app.nbr_captures)
        self.timer.start()

    def exit_actions(self):
        self.app.led_picture.switch_off()
        self.app.led_print.switch_off()

    def validate_transition(self, events):
        if self.timer.is_timeout():
            return 'capture'
Example #8
0
class StateChoose(State):
    def __init__(self, timeout):
        State.__init__(self, 'choose', 'chosen')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        with timeit("Show picture choice (no default set)"):
            self.app.window.show_choice()
        self.app.max_captures = None
        self.app.led_picture.blink()
        self.app.led_print.blink()
        self.timer.start()

    def do_actions(self, events):
        event = self.app.find_choice_event(events)
        if event:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
                self.app.max_captures = self.app.config.getint(
                    'PICTURE', 'captures')
            elif event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                self.app.max_captures = 1
            elif event.pin == self.app.button_picture:
                self.app.max_captures = self.app.config.getint(
                    'PICTURE', 'captures')
            elif event.pin == self.app.button_print:
                self.app.max_captures = 1

    def exit_actions(self):
        if self.app.max_captures == self.app.config.getint(
                'PICTURE', 'captures'):
            self.app.led_picture.switch_on()
            self.app.led_print.switch_off()
        elif self.app.max_captures == 1:
            self.app.led_print.switch_on()
            self.app.led_picture.switch_off()
        else:
            self.app.led_print.switch_off()
            self.app.led_picture.switch_off()

    def validate_transition(self, events):
        if self.app.max_captures:
            return self.next_name
        elif self.timer.is_timeout():
            return "wait"
Example #9
0
class StatePrint(State):
    def __init__(self):
        State.__init__(self, 'print')
        self.timer = PoolingTimer(
            self.app.config.getfloat('PRINTER', 'printer_delay'))
        self.printed = False

    def entry_actions(self):
        self.printed = False

        with timeit("Display the final picture"):
            self.app.window.set_print_number(
                len(self.app.printer.get_all_tasks()),
                self.app.printer_unavailable)
            self.app.window.show_print(self.app.previous_picture)

        self.app.led_print.blink()
        # Reset timeout in case of settings changed
        self.timer.timeout = self.app.config.getfloat('PRINTER',
                                                      'printer_delay')
        self.timer.start()

    def do_actions(self, events):
        if self.app.find_print_event(
                events) and self.app.previous_picture_file:

            with timeit("Send final picture to printer"):
                self.app.led_print.switch_on()
                self.app.printer.print_file(
                    self.app.previous_picture_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 validate_transition(self, events):
        if self.timer.is_timeout() or self.printed:
            if self.printed:
                self.app.window.set_print_number(
                    len(self.app.printer.get_all_tasks()),
                    self.app.printer_unavailable)
            return 'finish'
Example #10
0
    def preview_countdown(self, timeout, alpha=80):
        """Show a countdown of `timeout` seconds on the preview.
        Returns when the countdown is finished.
        """
        timeout = int(timeout)
        if timeout < 1:
            raise ValueError("Start time shall be greater than 0")

        shown = False
        first_loop = True
        timer = PoolingTimer(timeout)
        while not timer.is_timeout():
            remaining = int(timer.remaining() + 1)
            if not self._overlay or remaining != timeout:
                # Rebluid overlay only if remaining number has changed
                self._show_overlay(str(remaining), alpha)
                timeout = remaining
                shown = False

            updated_rect = None
            if self._preview_compatible:
                updated_rect = self._window.show_image(
                    self._get_preview_image())
            elif not shown:
                updated_rect = self._window.show_image(
                    self._get_preview_image())
                shown = True  # Do not update dummy preview until next overlay update

            if first_loop:
                timer.start(
                )  # Because first preview capture is longer than others
                first_loop = False

            pygame.event.pump()
            if updated_rect:
                pygame.display.update(updated_rect)

        self._show_overlay(
            LANGUAGES.get(PiConfigParser.language,
                          LANGUAGES['en']).get('smile_message'), alpha)
        self._window.show_image(self._get_preview_image())
Example #11
0
File: ptb.py Project: xuhui/pibooth
class StateChoose(State):

    def __init__(self, timeout):
        State.__init__(self, 'choose')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        with timeit("Show picture choice (nothing selected)"):
            self.app.window.show_choice(self.app.capt_choices)
        self.app.nbr_captures = None
        self.app.led_picture.blink()
        self.app.led_print.blink()
        self.timer.start()

    def do_actions(self, events):
        event = self.app.find_choice_event(events)
        if event:
            if (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT) \
                    or (event.type == BUTTON_DOWN and event.pin == self.app.button_picture):
                self.app.nbr_captures = self.app.capt_choices[0]
            elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT) \
                    or (event.type == BUTTON_DOWN and event.pin == self.app.button_print):
                self.app.nbr_captures = self.app.capt_choices[1]

    def exit_actions(self):
        if self.app.nbr_captures == self.app.capt_choices[0]:
            self.app.led_picture.switch_on()
            self.app.led_print.switch_off()
        elif self.app.nbr_captures == self.app.capt_choices[1]:
            self.app.led_print.switch_on()
            self.app.led_picture.switch_off()
        else:
            self.app.led_print.switch_off()
            self.app.led_picture.switch_off()

    def validate_transition(self, events):
        if self.app.nbr_captures:
            return 'chosen'
        elif self.timer.is_timeout():
            return 'wait'
Example #12
0
class StateChoose(State):

    def __init__(self, timeout):
        State.__init__(self, 'choose')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        with timeit("Show picture choice (nothing selected)"):
            self.app.window.set_print_number(0)  # Hide printer status
            self.app.window.show_choice(self.app.capture_choices)
        self.app.capture_nbr = None
        self.app.led_capture.blink()
        self.app.led_print.blink()
        self.timer.start()

    def do_actions(self, events):
        event = self.app.find_choice_event(events)
        if event:
            if event.key == pygame.K_LEFT:
                self.app.capture_nbr = self.app.capture_choices[0]
            elif event.key == pygame.K_RIGHT:
                self.app.capture_nbr = self.app.capture_choices[1]

    def exit_actions(self):
        if self.app.capture_nbr == self.app.capture_choices[0]:
            self.app.led_capture.switch_on()
            self.app.led_print.switch_off()
        elif self.app.capture_nbr == self.app.capture_choices[1]:
            self.app.led_print.switch_on()
            self.app.led_capture.switch_off()
        else:
            self.app.led_print.switch_off()
            self.app.led_capture.switch_off()

    def validate_transition(self, events):
        if self.app.capture_nbr:
            return 'chosen'
        elif self.timer.is_timeout():
            return 'wait'
Example #13
0
class StateFailSafe(State):
    def __init__(self, timeout):
        State.__init__(self, 'failsafe')
        self.timer = PoolingTimer(timeout)

    def entry_actions(self):
        self.app.dirname = None
        self.app.capture_nbr = None
        self.app.nbr_duplicates = 0
        self.app.previous_animated = []
        self.previous_picture = None
        self.previous_picture_file = None
        self.previous_print_picture_file = None
        self.web_upload_sucessful = False
        self.previous_print_picture_file_qith_qr = None
        self.previous_picture_qr_file = None
        self.previous_picture_qr_file_inverted = None
        self.app.camera.drop_captures()  # Flush previous captures
        self.app.window.show_oops()
        self.timer.start()

    def validate_transition(self, events):
        if self.timer.is_timeout():
            return 'wait'
Example #14
0
class PicturePlugin(object):
    """Plugin to build the final picture.
    """
    def __init__(self, plugin_manager):
        self._pm = plugin_manager
        self.factory_pool = PicturesFactoryPool()
        self.picture_destroy_timer = PoolingTimer(0)
        self.second_previous_picture = None

    def _reset_vars(self, app):
        """Destroy final picture (can not be used anymore).
        """
        self.factory_pool.clear()
        app.previous_picture = None
        app.previous_animated = None
        app.previous_picture_file = None

    @pibooth.hookimpl
    def pibooth_setup_picture_factory(self, cfg, opt_index, factory):
        factory.set_margin(cfg.getint('PICTURE', 'margin_thick'))

        backgrounds = cfg.gettuple('PICTURE', 'backgrounds', ('color', 'path'),
                                   2)
        factory.set_background(backgrounds[opt_index])

        overlays = cfg.gettuple('PICTURE', 'overlays', 'path', 2)
        if overlays[opt_index]:
            factory.set_overlay(overlays[opt_index])

        texts = [
            cfg.get('PICTURE', 'footer_text1').strip('"'),
            cfg.get('PICTURE', 'footer_text2').strip('"')
        ]
        colors = cfg.gettuple('PICTURE', 'text_colors', 'color', len(texts))
        text_fonts = cfg.gettuple('PICTURE', 'text_fonts', str, len(texts))
        alignments = cfg.gettuple('PICTURE', 'text_alignments', str,
                                  len(texts))
        if any(elem != '' for elem in texts):
            for params in zip(texts, text_fonts, colors, alignments):
                factory.add_text(*params)

        if cfg.getboolean('PICTURE', 'captures_cropping'):
            factory.set_cropping()

        if cfg.getboolean('GENERAL', 'debug'):
            factory.set_outlines()

    @pibooth.hookimpl
    def pibooth_cleanup(self):
        self.factory_pool.quit()

    @pibooth.hookimpl
    def state_failsafe_enter(self, app):
        self._reset_vars(app)

    @pibooth.hookimpl
    def state_wait_enter(self, cfg, app):
        animated = self.factory_pool.get()
        if animated:
            app.previous_animated = itertools.cycle(animated)

        # Reset timeout in case of settings changed
        self.picture_destroy_timer.timeout = max(
            0, cfg.getfloat('WINDOW', 'final_image_delay'))
        self.picture_destroy_timer.start()

    @pibooth.hookimpl
    def state_wait_do(self, cfg, app):
        if cfg.getfloat('WINDOW', 'final_image_delay'
                        ) > 0 and self.picture_destroy_timer.is_timeout():
            self._reset_vars(app)

    @pibooth.hookimpl
    def state_processing_enter(self, app):
        self.second_previous_picture = app.previous_picture
        self._reset_vars(app)

    @pibooth.hookimpl
    def state_processing_do(self, cfg, app):
        idx = app.capture_choices.index(app.capture_nbr)

        with timeit("Creating the final picture"):
            captures = app.camera.get_captures()
            factory = get_picture_factory(captures,
                                          cfg.get('PICTURE', 'orientation'))
            self._pm.hook.pibooth_setup_picture_factory(cfg=cfg,
                                                        opt_index=idx,
                                                        factory=factory)
            app.previous_picture = factory.build()

        savedir = cfg.getpath('GENERAL', 'directory')
        app.previous_picture_file = osp.join(
            savedir,
            osp.basename(app.dirname) + "_pibooth.jpg")
        factory.save(app.previous_picture_file)

        if cfg.getboolean('WINDOW', 'animate') and app.capture_nbr > 1:
            with timeit("Asyncronously generate pictures for animation"):
                for capture in captures:
                    factory = get_picture_factory(
                        (capture, ),
                        cfg.get('PICTURE', 'orientation'),
                        force_pil=True)
                    self._pm.hook.pibooth_setup_picture_factory(
                        cfg=cfg, opt_index=idx, factory=factory)
                    self.factory_pool.add(factory)

    @pibooth.hookimpl
    def state_print_do(self, cfg, app, events):
        if app.find_capture_event(events):
            with timeit("Putting the capture in the forget folder"):
                file_dir, file_name = osp.split(app.previous_picture_file)
                forget_dir = osp.join(file_dir, "forget")
                if not os.path.exists(forget_dir):
                    os.makedirs(forget_dir)
                os.rename(app.previous_picture_file,
                          osp.join(forget_dir, file_name))
                self._reset_vars(app)
                app.previous_picture = self.second_previous_picture

                # Deactivate the print function for the backuped picture
                # as we don't known how many times it has already been printed
                app.nbr_duplicates = cfg.getint('PRINTER',
                                                'max_duplicates') + 1
Example #15
0
class PicturePlugin(object):
    """Plugin to build the final picture.
    """
    def __init__(self, plugin_manager):
        self._pm = plugin_manager
        self.factory_pool = PicturesFactoryPool()
        self.picture_destroy_timer = PoolingTimer(0)
        self.second_previous_picture = None

    def _reset_vars(self, app):
        """Destroy final picture (can not be used anymore).
        """
        self.factory_pool.clear()
        app.previous_picture = None
        app.previous_animated = None
        app.previous_picture_file = None

    @pibooth.hookimpl(hookwrapper=True)
    def pibooth_setup_picture_factory(self, cfg, opt_index, factory):

        outcome = yield  # all corresponding hookimpls are invoked here
        factory = outcome.get_result() or factory

        factory.set_margin(cfg.getint('PICTURE', 'margin_thick'))

        backgrounds = cfg.gettuple('PICTURE', 'backgrounds', ('color', 'path'),
                                   2)
        factory.set_background(backgrounds[opt_index])

        overlays = cfg.gettuple('PICTURE', 'overlays', 'path', 2)
        if overlays[opt_index]:
            factory.set_overlay(overlays[opt_index])

        texts = [
            cfg.get('PICTURE', 'footer_text1').strip('"'),
            cfg.get('PICTURE', 'footer_text2').strip('"')
        ]
        colors = cfg.gettuple('PICTURE', 'text_colors', 'color', len(texts))
        text_fonts = cfg.gettuple('PICTURE', 'text_fonts', str, len(texts))
        alignments = cfg.gettuple('PICTURE', 'text_alignments', str,
                                  len(texts))
        if any(elem != '' for elem in texts):
            for params in zip(texts, text_fonts, colors, alignments):
                factory.add_text(*params)

        if cfg.getboolean('PICTURE', 'captures_cropping'):
            factory.set_cropping()

        if cfg.getboolean('GENERAL', 'debug'):
            factory.set_outlines()

        outcome.force_result(factory)

    @pibooth.hookimpl
    def pibooth_cleanup(self):
        self.factory_pool.quit()

    @pibooth.hookimpl
    def state_failsafe_enter(self, app):
        self._reset_vars(app)

    @pibooth.hookimpl
    def state_wait_enter(self, cfg, app):
        animated = self.factory_pool.get()
        if cfg.getfloat('WINDOW', 'wait_image_delay') == 0:
            # Do it here to avoid a transient display of the picture
            self._reset_vars(app)
        elif animated:
            app.previous_animated = itertools.cycle(animated)

        # Reset timeout in case of settings changed
        self.picture_destroy_timer.timeout = max(
            0, cfg.getfloat('WINDOW', 'wait_image_delay'))
        self.picture_destroy_timer.start()

    @pibooth.hookimpl
    def state_wait_do(self, cfg, app):
        if cfg.getfloat('WINDOW', 'wait_image_delay') > 0 and self.picture_destroy_timer.is_timeout()\
                and app.previous_picture_file:
            self._reset_vars(app)

    @pibooth.hookimpl
    def state_processing_enter(self, app):
        self.second_previous_picture = app.previous_picture
        self._reset_vars(app)

    @pibooth.hookimpl
    def state_processing_do(self, cfg, app):
        idx = app.capture_choices.index(app.capture_nbr)

        with timeit("Saving raw captures"):
            captures = app.camera.get_captures()

            for savedir in cfg.gettuple('GENERAL', 'directory', 'path'):
                rawdir = osp.join(savedir, "raw", app.capture_date)
                os.makedirs(rawdir)

                for capture in captures:
                    count = captures.index(capture)
                    capture.save(
                        osp.join(rawdir, "pibooth{:03}.jpg".format(count)))

        with timeit("Creating the final picture"):
            default_factory = get_picture_factory(
                captures, cfg.get('PICTURE', 'orientation'))
            factory = self._pm.hook.pibooth_setup_picture_factory(
                cfg=cfg, opt_index=idx, factory=default_factory)
            app.previous_picture = factory.build()

        for savedir in cfg.gettuple('GENERAL', 'directory', 'path'):
            app.previous_picture_file = osp.join(savedir, app.picture_filename)
            factory.save(app.previous_picture_file)

        if cfg.getboolean('WINDOW', 'animate') and app.capture_nbr > 1:
            with timeit("Asyncronously generate pictures for animation"):
                for capture in captures:
                    default_factory = get_picture_factory(
                        (capture, ),
                        cfg.get('PICTURE', 'orientation'),
                        force_pil=True,
                        dpi=200)
                    factory = self._pm.hook.pibooth_setup_picture_factory(
                        cfg=cfg, opt_index=idx, factory=default_factory)
                    factory.set_margin(factory._margin //
                                       3)  # 1/3 since DPI is divided by 3
                    self.factory_pool.add(factory)

    @pibooth.hookimpl
    def state_processing_exit(self, app):
        app.count.taken += 1  # Do it here because 'print' state can be skipped

    @pibooth.hookimpl
    def state_print_do(self, cfg, app, events):
        if app.find_capture_event(events):

            with timeit("Moving the picture in the forget folder"):

                for savedir in cfg.gettuple('GENERAL', 'directory', 'path'):
                    forgetdir = osp.join(savedir, "forget")
                    if not osp.isdir(forgetdir):
                        os.makedirs(forgetdir)
                    os.rename(osp.join(savedir, app.picture_filename),
                              osp.join(forgetdir, app.picture_filename))

            self._reset_vars(app)
            app.count.forgotten += 1
            app.previous_picture = self.second_previous_picture

            # Deactivate the print function for the backuped picture
            # as we don't known how many times it has already been printed
            app.count.remaining_duplicates = 0
Example #16
0
class PiApplication(object):
    def __init__(self, config, plugin_manager):
        self._pm = plugin_manager
        self._config = config

        # Create directories where pictures are saved
        for savedir in config.gettuple('GENERAL', 'directory', 'path'):
            if osp.isdir(savedir) and config.getboolean('GENERAL', 'debug'):
                shutil.rmtree(savedir)
            if not osp.isdir(savedir):
                os.makedirs(savedir)

        # Prepare the pygame module for use
        os.environ['SDL_VIDEO_CENTERED'] = '1'
        pygame.init()

        # Create window of (width, height)
        init_size = self._config.gettyped('WINDOW', 'size')
        init_debug = self._config.getboolean('GENERAL', 'debug')
        init_color = self._config.gettyped('WINDOW', 'background')
        init_text_color = self._config.gettyped('WINDOW', 'text_color')
        if not isinstance(init_color, (tuple, list)):
            init_color = self._config.getpath('WINDOW', 'background')

        title = 'Pibooth v{}'.format(pibooth.__version__)
        if not isinstance(init_size, str):
            self._window = PtbWindow(title,
                                     init_size,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)
        else:
            self._window = PtbWindow(title,
                                     color=init_color,
                                     text_color=init_text_color,
                                     debug=init_debug)

        self._menu = None
        self._multipress_timer = PoolingTimer(
            config.getfloat('CONTROLS', 'multi_press_delay'), False)

        # Define states of the application
        self._machine = StateMachine(self._pm, self._config, self,
                                     self._window)
        self._machine.add_state('wait')
        self._machine.add_state('choose')
        self._machine.add_state('chosen')
        self._machine.add_state('preview')
        self._machine.add_state('capture')
        self._machine.add_state('processing')
        self._machine.add_state('filter')
        self._machine.add_state('print')
        self._machine.add_state('finish')

        # ---------------------------------------------------------------------
        # Variables shared with plugins
        # Change them may break plugins compatibility
        self.capture_nbr = None
        self.capture_date = None
        self.capture_choices = (4, 1)
        self.previous_picture = None
        self.previous_animated = None
        self.previous_picture_file = None

        self.count = Counters(self._config.join_path("counters.pickle"),
                              taken=0,
                              printed=0,
                              forgotten=0,
                              remaining_duplicates=self._config.getint(
                                  'PRINTER', 'max_duplicates'))

        self.camera = camera.get_camera(
            config.getint('CAMERA', 'iso'),
            config.gettyped('CAMERA', 'resolution'),
            config.getint('CAMERA', 'rotation'),
            config.getboolean('CAMERA', 'flip'),
            config.getboolean('CAMERA', 'delete_internal_memory'))

        self.buttons = ButtonBoard(
            capture="BOARD" + config.get('CONTROLS', 'picture_btn_pin'),
            printer="BOARD" + config.get('CONTROLS', 'print_btn_pin'),
            hold_time=config.getfloat('CONTROLS', 'debounce_delay'),
            pull_up=True)
        self.buttons.capture.when_held = self._on_button_capture_held
        self.buttons.printer.when_held = self._on_button_printer_held

        self.leds = LEDBoard(
            capture="BOARD" + config.get('CONTROLS', 'picture_led_pin'),
            printer="BOARD" + config.get('CONTROLS', 'print_led_pin'))

        self.printer = Printer(config.get('PRINTER', 'printer_name'),
                               config.getint('PRINTER', 'max_pages'),
                               self.count)
        # ---------------------------------------------------------------------

    def _initialize(self):
        """Restore the application with initial parameters defined in the
        configuration file.
        Only parameters that can be changed at runtime are restored.
        """
        # Handle the language configuration
        language.CURRENT = self._config.get('GENERAL', 'language')
        fonts.CURRENT = fonts.get_filename(
            self._config.gettuple('PICTURE', 'text_fonts', str)[0])

        # Set the captures choices
        choices = self._config.gettuple('PICTURE', 'captures', int)
        for chx in choices:
            if chx not in [1, 2, 3, 4]:
                LOGGER.warning(
                    "Invalid captures number '%s' in config, fallback to '%s'",
                    chx, self.capture_choices)
                choices = self.capture_choices
                break
        self.capture_choices = choices

        # Handle autostart of the application
        self._config.handle_autostart()

        self._window.arrow_location = self._config.get('WINDOW', 'arrows')
        self._window.arrow_offset = self._config.getint(
            'WINDOW', 'arrows_x_offset')
        self._window.text_color = self._config.gettyped('WINDOW', 'text_color')
        self._window.drop_cache()

        # Handle window size
        size = self._config.gettyped('WINDOW', 'size')
        if isinstance(size, str) and size.lower() == 'fullscreen':
            if not self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        else:
            if self._window.is_fullscreen:
                self._window.toggle_fullscreen()
        self._window.debug = self._config.getboolean('GENERAL', 'debug')

        # Handle debug mode
        if not self._config.getboolean('GENERAL', 'debug'):
            set_logging_level()  # Restore default level
            self._machine.add_failsafe_state('failsafe')
        else:
            set_logging_level(logging.DEBUG)
            self._machine.remove_state('failsafe')

        # Reset the print counter (in case of max_pages is reached)
        self.printer.max_pages = self._config.getint('PRINTER', 'max_pages')

    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 _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)

    @property
    def picture_filename(self):
        """Return the final picture file name.
        """
        if not self.capture_date:
            raise EnvironmentError(
                "The 'capture_date' attribute is not set yet")
        return "{}_pibooth.jpg".format(self.capture_date)

    def find_quit_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.QUIT:
                return event
        return None

    def find_settings_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                return event
            if event.type == BUTTONDOWN and event.capture and event.printer:
                return event
        return None

    def find_fullscreen_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and \
                    event.key == pygame.K_f and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
        return None

    def find_resize_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.VIDEORESIZE:
                return event
        return None

    def find_capture_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                rect = self._window.get_rect()
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    return event
            if event.type == BUTTONDOWN and event.capture:
                return event
        return None

    def find_print_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_e\
                        and pygame.key.get_mods() & pygame.KMOD_CTRL:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                rect = self._window.get_rect()
                if pygame.Rect(rect.width // 2, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    return event
            if event.type == BUTTONDOWN and event.printer:
                return event
        return None

    def find_print_status_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == PRINTER_TASKS_UPDATED:
                return event
        return None

    def find_choice_event(self, events):
        """Return the first found event if found in the list.
        """
        for event in events:
            if event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT:
                return event
            if event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT:
                return event
            if event.type == pygame.MOUSEBUTTONUP and event.button in (1, 2,
                                                                       3):
                # Don't consider the mouse wheel (button 4 & 5):
                print("MOUSEBUTTONUP")
                rect = self._window.get_rect()
                x, y = event.pos
                if pygame.Rect(0, 0, rect.width // 2,
                               rect.height).collidepoint(event.pos):
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
            if event.type == BUTTONDOWN:
                if event.capture:
                    event.key = pygame.K_LEFT
                else:
                    event.key = pygame.K_RIGHT
                return event
        return None

    def main_loop(self):
        """Run the main game loop.
        """
        try:
            fps = 40
            clock = pygame.time.Clock()
            self._initialize()
            self._pm.hook.pibooth_startup(cfg=self._config, app=self)
            self._machine.set_state('wait')

            while True:
                events = list(pygame.event.get())

                if self.find_quit_event(events):
                    break

                if self.find_fullscreen_event(events):
                    self._window.toggle_fullscreen()

                event = self.find_resize_event(events)
                if event:
                    self._window.resize(event.size)

                if not self._menu and self.find_settings_event(events):
                    self.camera.stop_preview()
                    self.leds.off()
                    self._menu = PiConfigMenu(self._window, self._config,
                                              self.count)
                    self._menu.show()
                    self.leds.blink(on_time=0.1, off_time=1)
                elif self._menu and self._menu.is_shown():
                    self._menu.process(events)
                elif self._menu and not self._menu.is_shown():
                    self.leds.off()
                    self._initialize()
                    self._machine.set_state('wait')
                    self._menu = None
                else:
                    self._machine.process(events)

                pygame.display.update()
                clock.tick(
                    fps
                )  # Ensure the program will never run at more than <fps> frames per second

        except Exception as ex:
            LOGGER.error(str(ex), exc_info=True)
            LOGGER.error(get_crash_message())
        finally:
            self._pm.hook.pibooth_cleanup(app=self)
            pygame.quit()
Example #17
0
class ViewPlugin(object):
    """Plugin to manage the pibooth window dans transitions.
    """
    def __init__(self, plugin_manager):
        self._pm = plugin_manager
        self.count = 0
        self.forgotten = False
        # Seconds to display the failed message
        self.failed_view_timer = PoolingTimer(2)
        # Seconds between each animated frame
        self.animated_frame_timer = PoolingTimer(0)
        # Seconds before going back to the start
        self.choose_timer = PoolingTimer(30)
        # Seconds to display the selected layout
        self.layout_timer = PoolingTimer(4)
        # Seconds to display the selected layout
        self.print_view_timer = PoolingTimer(0)
        # Seconds to display the selected layout
        self.finish_timer = PoolingTimer(1)

    @pibooth.hookimpl
    def state_failsafe_enter(self, win):
        win.show_oops()
        self.failed_view_timer.start()
        LOGGER.error(get_crash_message())

    @pibooth.hookimpl
    def state_failsafe_validate(self):
        if self.failed_view_timer.is_timeout():
            return 'wait'

    @pibooth.hookimpl
    def state_wait_enter(self, cfg, app, win):
        self.forgotten = False
        if app.previous_animated:
            previous_picture = next(app.previous_animated)
            # Reset timeout in case of settings changed
            self.animated_frame_timer.timeout = cfg.getfloat(
                'WINDOW', 'animate_delay')
            self.animated_frame_timer.start()
        else:
            previous_picture = app.previous_picture

        win.show_intro(
            previous_picture,
            app.printer.is_available() and app.count.remaining_duplicates > 0)
        if app.printer.is_installed():
            win.set_print_number(len(app.printer.get_all_tasks()),
                                 not app.printer.is_available())

    @pibooth.hookimpl
    def state_wait_do(self, app, win, events):
        if app.previous_animated and self.animated_frame_timer.is_timeout():
            previous_picture = next(app.previous_animated)
            win.show_intro(
                previous_picture,
                app.printer.is_available()
                and app.count.remaining_duplicates > 0)
            self.animated_frame_timer.start()
        else:
            previous_picture = app.previous_picture

        event = app.find_print_status_event(events)
        if event and app.printer.is_installed():
            win.set_print_number(len(event.tasks),
                                 not app.printer.is_available())

        if app.find_print_event(events) or (win.get_image()
                                            and not previous_picture):
            win.show_intro(
                previous_picture,
                app.printer.is_available()
                and app.count.remaining_duplicates > 0)

    @pibooth.hookimpl
    def state_wait_validate(self, app, events):
        if app.find_capture_event(events):
            if len(app.capture_choices) > 1:
                return 'choose'
            return 'preview'  # No choice

    @pibooth.hookimpl
    def state_wait_exit(self, win):
        self.count = 0
        win.show_image(None)  # Clear currently displayed image

    @pibooth.hookimpl
    def state_choose_enter(self, app, win):
        with timeit("Show picture choice (nothing selected)"):
            win.set_print_number(0, False)  # Hide printer status
            win.show_choice(app.capture_choices)
        self.choose_timer.start()

    @pibooth.hookimpl
    def state_choose_validate(self, app):
        if app.capture_nbr:
            return 'chosen'
        elif self.choose_timer.is_timeout():
            return 'wait'

    @pibooth.hookimpl
    def state_chosen_enter(self, app, win):
        with timeit("Show picture choice ({} captures selected)".format(
                app.capture_nbr)):
            win.show_choice(app.capture_choices, selected=app.capture_nbr)
        self.layout_timer.start()

    @pibooth.hookimpl
    def state_chosen_validate(self):
        if self.layout_timer.is_timeout():
            return 'preview'

    @pibooth.hookimpl
    def state_preview_enter(self, app, win):
        self.count += 1
        win.set_capture_number(self.count, app.capture_nbr)

    @pibooth.hookimpl
    def state_preview_validate(self):
        return 'capture'

    @pibooth.hookimpl
    def state_capture_do(self, app, win):
        win.set_capture_number(self.count, app.capture_nbr)

    @pibooth.hookimpl
    def state_capture_validate(self, app):
        if self.count >= app.capture_nbr:
            return 'processing'
        return 'preview'

    @pibooth.hookimpl
    def state_processing_enter(self, win):
        win.show_work_in_progress()

    @pibooth.hookimpl
    def state_processing_validate(self, cfg, app):
        if app.printer.is_available() and cfg.getfloat('PRINTER', 'printer_delay') > 0\
                and app.count.remaining_duplicates > 0:
            return 'print'
        return 'finish'  # Can not print

    @pibooth.hookimpl
    def state_print_enter(self, cfg, app, win):
        with timeit("Display the final picture"):
            win.set_print_number(len(app.printer.get_all_tasks()),
                                 not app.printer.is_available())
            win.show_print(app.previous_picture)

        # Reset timeout in case of settings changed
        self.print_view_timer.timeout = cfg.getfloat('PRINTER',
                                                     'printer_delay')
        self.print_view_timer.start()

    @pibooth.hookimpl
    def state_print_validate(self, app, win, events):
        printed = app.find_print_event(events)
        self.forgotten = app.find_capture_event(events)
        if self.print_view_timer.is_timeout() or printed or self.forgotten:
            if printed:
                win.set_print_number(len(app.printer.get_all_tasks()),
                                     not app.printer.is_available())
            return 'finish'

    @pibooth.hookimpl
    def state_finish_enter(self, cfg, app, win):
        if cfg.getfloat('WINDOW',
                        'finish_image_delay') > 0 and not self.forgotten:
            win.show_finished(app.previous_picture)
            timeout = cfg.getfloat('WINDOW', 'finish_image_delay')
        else:
            win.show_finished()
            timeout = 1

        # Reset timeout in case of settings changed
        self.finish_timer.timeout = timeout
        self.finish_timer.start()

    @pibooth.hookimpl
    def state_finish_validate(self):
        if self.finish_timer.is_timeout():
            return 'wait'
Example #18
0
class StateWait(State):
    def __init__(self):
        State.__init__(self, 'wait')
        self.timer = PoolingTimer(
            self.app.config.getfloat('WINDOW', 'animate_delay'))
        if self.app.config.getfloat('WINDOW', 'final_image_delay') < 0:
            self.final_display_timer = None
        else:
            self.final_display_timer = PoolingTimer(
                self.app.config.getfloat('WINDOW', 'final_image_delay'))

    def entry_actions(self):
        animated = self.app.makers_pool.get()
        if self.app.config.getfloat('WINDOW', 'final_image_delay') < 0:
            self.final_display_timer = None
        else:
            self.final_display_timer = PoolingTimer(
                self.app.config.getfloat('WINDOW', 'final_image_delay'))

        if self.final_display_timer and self.final_display_timer.is_timeout():
            previous_picture = None
        elif self.app.config.getboolean('WINDOW', 'animate') and animated:
            self.app.previous_animated = itertools.cycle(animated)
            previous_picture = next(self.app.previous_animated)
            self.timer.timeout = self.app.config.getfloat(
                'WINDOW', 'animate_delay')
            self.timer.start()
        else:
            previous_picture = self.app.previous_picture

        self.app.window.show_intro(
            previous_picture,
            self.app.printer.is_installed()
            and self.app.nbr_duplicates < self.app.config.getint(
                'PRINTER', 'max_duplicates')
            and not self.app.printer_unavailable)
        self.app.window.set_print_number(len(self.app.printer.get_all_tasks()),
                                         self.app.printer_unavailable)

        self.app.led_capture.blink()
        if self.app.previous_picture_file and self.app.printer.is_installed(
        ) and not self.app.printer_unavailable:
            self.app.led_print.blink()

    def do_actions(self, events):
        if self.app.config.getboolean(
                'WINDOW', 'animate'
        ) and self.app.previous_animated and self.timer.is_timeout():
            previous_picture = next(self.app.previous_animated)
            self.app.window.show_intro(
                previous_picture,
                self.app.printer.is_installed()
                and self.app.nbr_duplicates < self.app.config.getint(
                    'PRINTER', 'max_duplicates')
                and not self.app.printer_unavailable)
            self.timer.start()
        else:
            previous_picture = self.app.previous_picture

        if self.app.find_print_event(events) and self.app.previous_picture_file and self.app.printer.is_installed()\
                and not (self.final_display_timer and self.final_display_timer.is_timeout()):

            if self.app.nbr_duplicates >= self.app.config.getint(
                    'PRINTER', 'max_duplicates'):
                LOGGER.warning(
                    "Too many duplicates sent to the printer (%s max)",
                    self.app.config.getint('PRINTER', 'max_duplicates'))
                return

            elif self.app.printer_unavailable:
                LOGGER.warning(
                    "Maximum number of printed pages reached (%s/%s max)",
                    self.app.printer.nbr_printed,
                    self.app.config.getint('PRINTER', 'max_pages'))
                return

            with timeit("Send final picture to printer"):
                self.app.led_print.switch_on()
                self.app.printer.print_file(
                    self.app.previous_picture_file,
                    self.app.config.getint('PRINTER', 'pictures_per_page'))

            time.sleep(1)  # Just to let the LED switched on
            self.app.nbr_duplicates += 1

            if self.app.nbr_duplicates >= self.app.config.getint(
                    'PRINTER',
                    'max_duplicates') or self.app.printer_unavailable:
                self.app.window.show_intro(previous_picture, False)
                self.app.led_print.switch_off()
            else:
                self.app.led_print.blink()

        event = self.app.find_print_status_event(events)
        if event:
            self.app.window.set_print_number(len(event.tasks),
                                             self.app.printer_unavailable)

        if self.final_display_timer and self.final_display_timer.is_timeout():
            self.app.window.show_intro(None, False)

    def exit_actions(self):
        self.app.led_capture.switch_off()
        self.app.led_print.switch_off()

        # Clear currently displayed image
        self.app.window.show_image(None)

    def validate_transition(self, events):
        if self.app.find_capture_event(events):
            if len(self.app.capture_choices) > 1:
                return 'choose'
            else:
                self.app.capture_nbr = self.app.capture_choices[0]
                return 'capture'