Example #1
0
class ResourceReader:
    config=None

    def __init__(self):
        self.mon=Monitor()
        self.mon.on()
        
    def read(self,pp_dir,pp_home):
        if ResourceReader.config==None:
            tryfile=pp_home+os.sep+"resources.cfg"
            if os.path.exists(tryfile):
                 filename=tryfile
            else:
                self.mon.log(self,"Resources not found at "+ tryfile)
                tryfile=pp_dir+os.sep+'pp_home'+os.sep+"resources.cfg"
                if os.path.exists(tryfile):
                    filename=tryfile
                else:
                    self.mon.log(self,"Resources not found at "+ tryfile)
                    self.mon.err(self,"resources.cfg not found")
                    return False   
            ResourceReader.config = ConfigParser.ConfigParser()
            ResourceReader.config.read(filename)
            self.mon.log(self,"Read resources from "+ filename)
            return True

    def get(self,section,item):
        if ResourceReader.config.has_option(section,item)==False:
            return False
        else:
            return ResourceReader.config.get(section,item)
Example #2
0
class ResourceReader:
    config = None

    def __init__(self):
        self.mon = Monitor()
        self.mon.on()

    def read(self, pp_dir, pp_home):
        if ResourceReader.config == None:
            tryfile = pp_home + os.sep + "resources.cfg"
            if os.path.exists(tryfile):
                filename = tryfile
            else:
                self.mon.log(self, "Resources not found at " + tryfile)
                tryfile = pp_dir + os.sep + 'pp_home' + os.sep + "resources.cfg"
                if os.path.exists(tryfile):
                    filename = tryfile
                else:
                    self.mon.log(self, "Resources not found at " + tryfile)
                    self.mon.err(self, "resources.cfg not found")
                    return False
            ResourceReader.config = ConfigParser.ConfigParser()
            ResourceReader.config.read(filename)
            self.mon.log(self, "Read resources from " + filename)
            return True

    def get(self, section, item):
        if ResourceReader.config.has_option(section, item) == False:
            return False
        else:
            return ResourceReader.config.get(section, item)
class KbdDriver(object):

    config = None

    def __init__(self):
        self.mon = Monitor()

    # sets up tkinter keyboard events such that any key press
    # does a callback to 'callback' with the event object and a symbolic name.
    def bind_keys(self, widget, callback):
        for option in KbdDriver.config.items("keys"):
            condition = option[0]
            symbolic_name = option[1]
            # print condition,symbolic_name
            widget.bind(condition, lambda event, name=symbolic_name: self.specific_key(callback, name))

        # bind all the normal keys that return a printing character such that x produces pp-key-x
        widget.bind("<Key>", lambda event: self.normal_key(callback, event))

    def specific_key(self, callback, name):
        callback(name, "KBD")

    # alphanumeric keys- convert to symbolic by adding pp-key-
    def normal_key(self, callback, event):
        key = event.char
        if key != "":
            callback("pp-key-" + key, "KBD")

    # read the key bindings from keys.cfg
    def read(self, pp_dir, pp_home, pp_profile):
        if KbdDriver.config is None:
            # try inside profile
            tryfile = pp_profile + os.sep + "pp_io_config" + os.sep + "keys.cfg"
            if os.path.exists(tryfile):
                self.mon.log(self, "Found keys.cfg in profile at: " + tryfile)
                filename = tryfile
            else:
                # try inside pipresents
                tryfile = pp_dir + os.sep + "pp_config" + os.sep + "keys.cfg"
                if os.path.exists(tryfile):
                    filename = tryfile
                    self.mon.log(self, "Fallback keys.cfg found at " + tryfile)
                else:
                    self.mon.log(self, "keys.cfg not found at " + tryfile)
                    self.mon.err(self, "keys.cfg not found in profile or fallback in Pi Presents")
                    return False
            KbdDriver.config = ConfigParser.ConfigParser()
            KbdDriver.config.optionxform = str
            KbdDriver.config.read(filename)
            self.mon.log(self, "keys.cfg read from " + filename)
            if KbdDriver.config.has_section("keys") is False:
                self.mon.err(self, "no [keys] section in keys.cfg")
                return False
            return True

    def has_section(self, section):
        if KbdDriver.config.has_section(section) is False:
            return False
class ResourceReader(object):
    config = None

    def __init__(self):
        self.mon = Monitor()
        self.mon.on()

    def read(self, pp_dir, pp_home, pp_profile):
        """
        looks for resources.cfg in the profile, then in pp_home, then in the pi_presents directory.
        returns True if it finds the resources.cfg, otherwise returns False

        ::param pp_dir: the PiPresents directory
        ::param pp_home: the current pp_home directory
        ::param pp_profile: the current profile directory
        """
        if not ResourceReader.config:
            profile_config = os.path.join(pp_profile, "resources.cfg")
            home_config = os.path.join(pp_home, "resources.cfg")
            pp_config = os.path.join(pp_dir, 'pp_home', "resources.cfg")

            # try inside profile
            if os.path.exists(profile_config):
                config_path = profile_config

            # try inside pp_home
            elif os.path.exists(home_config):
                config_path = home_config

            # try in the pi presents directory
            elif os.path.exists(pp_config):
                config_path = pp_config

            else:
                # throw an error if we can't find any config files
                self.mon.err(self, "resources.cfg not found at {0}, {1} or {2}".format(profile_config, home_config, pp_config))
                return False

            ResourceReader.config = ConfigParser.ConfigParser()
            ResourceReader.config.read(config_path)
            self.mon.log(self, "resources.cfg read from " + config_path)
            return True

    def get(self, section, item):
        if not ResourceReader.config.has_option(section, item):
            return False
        else:
            return ResourceReader.config.get(section, item)
class ResourceReader:
    config = None

    def __init__(self):
        self.mon = Monitor()
        self.mon.on()

    def read(self, pp_dir, pp_home, pp_profile):
        if ResourceReader.config == None:
            # try inside profile
            tryfile = pp_profile + os.sep + "resources.cfg"
            # self.mon.log(self,"Trying resources.cfg in profile at: "+ tryfile)
            if os.path.exists(tryfile):
                filename = tryfile
            else:
                # try inside pp_home
                # self.mon.log(self,"resources.cfg not found at "+ tryfile+ " trying pp_home")
                tryfile = pp_home + os.sep + "resources.cfg"
                if os.path.exists(tryfile):
                    filename = tryfile
                else:
                    # try inside pipresents
                    # self.mon.log(self,"resources.cfg not found at "+ tryfile + " trying inside pipresents")
                    tryfile = pp_dir + os.sep + 'pp_home' + os.sep + "resources.cfg"
                    if os.path.exists(tryfile):
                        filename = tryfile
                    else:
                        self.mon.log(self,
                                     "resources.cfg not found at " + tryfile)
                        self.mon.err(self, "resources.cfg not found")
                        return False
            ResourceReader.config = ConfigParser.ConfigParser()
            ResourceReader.config.read(filename)
            self.mon.log(self, "resources.cfg read from " + filename)
            return True

    def get(self, section, item):
        if ResourceReader.config.has_option(section, item) == False:
            return False
        else:
            return ResourceReader.config.get(section, item)
class ResourceReader:
    config=None

    def __init__(self):
        self.mon=Monitor()
        self.mon.on()

    def read(self,pp_dir,pp_home,pp_profile):
        if ResourceReader.config==None:
            # try inside profile
            tryfile=pp_profile+os.sep+"resources.cfg"
            # self.mon.log(self,"Trying resources.cfg in profile at: "+ tryfile)
            if os.path.exists(tryfile):
                 filename=tryfile
            else:
                # try inside pp_home
                # self.mon.log(self,"resources.cfg not found at "+ tryfile+ " trying pp_home")
                tryfile=pp_home+os.sep+"resources.cfg"
                if os.path.exists(tryfile):
                    filename=tryfile
                else:
                    # try inside pipresents
                    # self.mon.log(self,"resources.cfg not found at "+ tryfile + " trying inside pipresents")
                    tryfile=pp_dir+os.sep+'pp_home'+os.sep+"resources.cfg"
                    if os.path.exists(tryfile):
                        filename=tryfile
                    else:
                        self.mon.log(self,"resources.cfg not found at "+ tryfile)
                        self.mon.err(self,"resources.cfg not found")
                        return False   
            ResourceReader.config = ConfigParser.ConfigParser()
            ResourceReader.config.read(filename)
            self.mon.log(self,"resources.cfg read from "+ filename)
            return True
        

    def get(self,section,item):
        if ResourceReader.config.has_option(section,item)==False:
            return False
        else:
            return ResourceReader.config.get(section,item)
Example #7
0
class MediaShow:

    # *******************
    # External interface
    # ********************

    def __init__(self, show, canvas, showlist, pp_home, pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the dictionary fo the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon = Monitor()
        self.mon.on()

        #instantiate arguments
        self.show = show
        self.showlist = showlist
        self.canvas = canvas
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # open resources
        self.rr = ResourceReader()

        # Init variables
        self.player = None
        self.shower = None
        self._poll_for_interval_timer = None
        self._poll_for_continue_timer = None
        self._waiting_for_interval = False
        self._interval_timer = None
        self.error = False

        self._interval_timer_signal = False
        self._end_mediashow_signal = False
        self._next_track_signal = False
        self._previous_track_signal = False
        self._play_child_signal = False
        self._req_next = 'nil'

        self._state = 'closed'

    def play(self,
             end_callback,
             ready_callback=None,
             top=False,
             command='nil'):
        """ displays the mediashow
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """

        #instantiate the arguments
        self._end_callback = end_callback
        self._ready_callback = ready_callback
        self.top = top
        self.command = command
        self.mon.log(self, "Starting show: " + self.show['show-ref'])

        # check  data files are available.
        self.media_file = self.pp_profile + "/" + self.show['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self, "Medialist file not found: " + self.media_file)
            self._end('error', "Medialist file not found")

        #create a medialist for the mediashow and read it.
        self.medialist = MediaList()
        if self.medialist.open_list(self.media_file,
                                    self.showlist.sissue()) == False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self._end('error', "Version of medialist different to Pi Presents")

        self._wait_for_trigger()

# respond to key presses.

    def key_pressed(self, key_name):
        self.mon.log(self, "received key: " + key_name)

        if key_name == '':
            pass

        elif key_name == 'escape':
            # if next lower show is running pass down to stop the show and lower level
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            # if not at top stop the show
            else:
                if self.top == False:
                    self._end_mediashow_signal = True
                    # and if a track is running stop that first
                    if self.player <> None:
                        self.player.key_pressed(key_name)
                else:
                    # at top level in a manual presentation stop the track
                    if self.show['progress'] == 'manual':
                        if self.player <> None:
                            self.player.key_pressed(key_name)

        elif key_name in ('up', 'down'):
            # if child or sub-show is running and is a show pass to show, track does not use up/down
            # otherwise use keys for next or previous
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            else:
                if key_name == 'up':
                    self._previous()
                else:
                    self._next()

        elif key_name == 'return':
            # if child show or sub-show is running and is show - pass down- player does not use return
            # ELSE use Return to start child or to start the show if waiting
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            else:
                if self._state == 'playing':
                    if self.show['has-child'] == 'yes':
                        self._play_child_signal = True
                        # and stop the current track if its running
                        if self.player <> None:
                            self.player.key_pressed("escape")
                else:
                    self._start_show()

        elif key_name == 'pir':
            self._start_show()

        elif key_name in ('p', ' '):
            # pass down if show or track running.
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            elif self.player <> None:
                self.player.key_pressed(key_name)

    def button_pressed(self, button, edge):
        if button == 'play': self.key_pressed("return")
        elif button == 'up': self.key_pressed("up")
        elif button == 'down': self.key_pressed("down")
        elif button == 'stop': self.key_pressed("escape")
        elif button == 'pause': self.key_pressed('p')
        elif button == 'PIR': self.key_pressed('pir')

    # kill or error
    def terminate(self, reason):
        if self.shower <> None:
            self.mon.log(self, "sent terminate to shower")
            self.shower.terminate(reason)
        elif self.player <> None:
            self.mon.log(self, "sent terminate to player")
            self.player.terminate(reason)
        else:
            self._end(reason,
                      'terminated without terminating shower or player')

    def _tidy_up(self):
        if self._poll_for_continue_timer <> None:
            self.canvas.after_cancel(self._poll_for_continue_timer)
            self._poll_for_continue_timer = None
        if self._poll_for_interval_timer <> None:
            self.canvas.after_cancel(self._poll_for_interval_timer)
            self._poll_for_interval_timer = None
        if self._interval_timer <> None:
            self.canvas.after_cancel(self._interval_timer)
            self._interval_timer = None

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self.terminate("error")
        else:
            return value

# ***************************
# Respond to key/button presses
# ***************************

    def _stop(self, message):
        self._end_mediashow_signal = True
        if self._interval_timer <> None:
            self.canvas.after_cancel(self._interval_timer)

    def _next(self):
        # stop track if running and set signal
        self._next_track_signal = True
        if self.shower <> None:
            self.shower.key_pressed("escape")
        else:
            if self.player <> None:
                self.player.key_pressed("escape")

    def _previous(self):
        self._previous_track_signal = True
        if self.shower <> None:
            self.shower.key_pressed("escape")
        else:
            if self.player <> None:
                self.player.key_pressed("escape")

# ***************************
# end of show functions
# ***************************

    def _end(self, reason, message):
        self._end_mediashow_signal = False
        self.mon.log(self, "Ending Mediashow: " + self.show['show-ref'])
        self._tidy_up()
        self._end_callback(reason, message)
        self = None
        return

# ***************************
# Show sequencer
# ***************************

    def _wait_for_trigger(self):
        self._state = 'waiting'
        if self.ready_callback <> None:
            self.ready_callback()

        self.mon.log(self, "Waiting for trigger: " + self.show['trigger'])

        if self.show['trigger'] == "button":
            # blank screen waiting for trigger if auto, otherwise display something
            if self.show['progress'] == "manual":
                text = self.resource('mediashow', 'm01')
            else:
                text = ""
            self.display_message(self.canvas, 'text', text, 0,
                                 self._start_show)

        elif self.show['trigger'] == "PIR":
            # blank screen waiting for trigger
            text = self.resource('mediashow', 'm02')
            self.display_message(self.canvas, 'text', text, 0,
                                 self._start_show)

        elif self.show['trigger'] == "start":
            self._start_show()

        else:
            self.mon.err(self, "Unknown trigger: " + self.show['trigger'])
            self._end('error', "Unknown trigger type")

    def _start_show(self):
        self._state = 'playing'
        self._direction = 'forward'
        # start interval timer
        if self.show[
                'repeat'] == "interval" and self.show['repeat-interval'] <> 0:
            self._interval_timer_signal = False
            self._interval_timer = self.canvas.after(
                int(self.show['repeat-interval']) * 1000,
                self._end_interval_timer)
        # and play the first track unless commanded otherwise
        if self.command == 'backward':
            self.medialist.finish()
        else:
            self.medialist.start()
        self._play_selected_track(self.medialist.selected_track())

    def _what_next(self):
        self._direction = 'forward'

        # user wants to end, wait for any shows or tracks to have ended then end show
        if self._end_mediashow_signal == True:
            if self.player == None and self.shower == None:
                self._end_mediashow_signal = False
                self._end('normal', "show ended by user")
            else:
                pass

        #returning from a subshow needing to move onward
        elif self._req_next == 'do-next':
            self._req_next = 'nil'
            self.medialist.next()
            self._play_selected_track(self.medialist.selected_track())

        #returning from a subshow needing to move backward
        elif self._req_next == 'do-previous':
            self._req_next = 'nil'
            self._direction = 'backward'
            self.medialist.previous()
            self._play_selected_track(self.medialist.selected_track())

        # user wants to play child
        elif self._play_child_signal == True:
            self._play_child_signal = False
            index = self.medialist.index_of_track('pp-child-show')
            if index >= 0:
                #don't select the track as need to preserve mediashow sequence.
                child_track = self.medialist.track(index)
                self._display_eggtimer(self.resource('mediashow', 'm07'))
                self._play_selected_track(child_track)
            else:
                self.mon.err(
                    self, "Child show not found in medialist: " +
                    self.show['pp-child-show'])
                self._end('error', "child show not found in medialist")

        # skip to next track on user input
        elif self._next_track_signal == True:
            self._next_track_signal = False
            if self.medialist.at_end() == True:
                if self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'oneshot' and self.top == False:
                    self._end('do-next', "Return from Sub Show")
                else:
                    self.medialist.next()
                    self._play_selected_track(self.medialist.selected_track())
            else:
                self.medialist.next()
                self._play_selected_track(self.medialist.selected_track())

        # skip to previous track on user input
        elif self._previous_track_signal == True:
            self._previous_track_signal = False
            self._direction = 'backward'
            if self.medialist.at_start() == True:
                if self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'oneshot' and self.top == False:
                    self._end('do-previous', "Return from Sub Show")
                else:
                    self.medialist.previous()
                    self._play_selected_track(self.medialist.selected_track())
            else:
                self.medialist.previous()
                self._play_selected_track(self.medialist.selected_track())

        # track is finished and we are on auto
        elif self.show['progress'] == "auto":
            if self.medialist.at_end() == True:
                if self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'oneshot' and self.top == False:
                    self._end('do-next', "Return from Sub Show")

                #### elif
                elif self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'oneshot' and self.top == True:
                    self._wait_for_trigger()

                elif self._waiting_for_interval == True:
                    if self._interval_timer_signal == True:
                        self._interval_timer_signal = False
                        self._waiting_for_interval = False
                        self._start_show()
                    else:
                        self._poll_for_interval_timer = self.canvas.after(
                            1000, self._what_next)

                elif self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'interval' and int(
                            self.show['repeat-interval']) > 0:
                    self._waiting_for_interval = True
                    self._poll_for_interval_timer = self.canvas.after(
                        1000, self._what_next)

                elif self.show['sequence'] == "ordered" and self.show[
                        'repeat'] == 'interval' and int(
                            self.show['repeat-interval']) == 0:
                    self.medialist.next()
                    self._play_selected_track(self.medialist.selected_track())

                else:
                    self.mon.err(self, "Unhandled playing event: ")
                    self._end('error', "Unhandled playing event")

            else:
                self.medialist.next()
                self._play_selected_track(self.medialist.selected_track())

        # track has finished and we are on manual progress
        elif self.show['progress'] == "manual":
            self._delete_eggtimer()
            self._display_eggtimer(self.resource('mediashow', 'm03'))
            self._poll_for_continue_timer = self.canvas.after(
                500, self._what_next)

        else:
            #unhandled state
            self.mon.err(self, "Unhandled playing event: ")
            self._end('error', "Unhandled playing event")

    def _end_interval_timer(self):
        self._interval_timer_signal = True


# ***************************
# Dispatching to Players/Shows
# ***************************

# used to display internal messages in situations where a medialist entry could be used.

    def display_message(self, canvas, source, content, duration,
                        _display_message_callback):
        self._display_message_callback = _display_message_callback
        tp = {
            'duration': duration,
            'message-colour': 'white',
            'message-font': 'Helvetica 20 bold'
        }
        self.player = MessagePlayer(canvas, tp, tp)
        self.player.play(content, self._display_message_end, None)

    def _display_message_end(self, reason, message):
        self.player = None
        if reason in ('error', 'killed'):
            self._end(reason, message)
        else:
            self._display_message_callback()

    def complete_path(self, selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Track to play is: " + track_file)
        return track_file

    def _play_selected_track(self, selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
        self.canvas.delete(ALL)
        if self.show['progress'] == "manual":
            self._display_eggtimer(self.resource('mediashow', 'm04'))

        # is menu required
        if self.show['has-child'] == "yes":
            enable_child = True
        else:
            enable_child = False

        #dispatch track by type
        self.player = None
        self.shower = None
        track_type = selected_track['type']
        self.mon.log(self, "Track type is: " + track_type)

        if track_type == "video":
            # create a videoplayer
            track_file = self.complete_path(selected_track)
            self.player = VideoPlayer(self.canvas, self.show, selected_track)
            self.player.play(track_file,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=enable_child)

        elif track_type == "image":
            track_file = self.complete_path(selected_track)
            # images played from menus don't have children
            self.player = ImagePlayer(self.canvas, self.show, selected_track)
            self.player.play(track_file,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=enable_child)

        elif track_type == "message":
            # bit odd because MessagePlayer is used internally to display text.
            text = selected_track['text']
            self.player = MessagePlayer(self.canvas, self.show, selected_track)
            self.player.play(text,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=enable_child)

        elif track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >= 0:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
            else:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self._end('error', "Unknown show")

            if selected_show['type'] == "mediashow":
                self.shower = MediaShow(selected_show, self.canvas,
                                        self.showlist, self.pp_home,
                                        self.pp_profile)
                self.shower.play(self.end_shower,
                                 top=False,
                                 command=self._direction)

            elif selected_show['type'] == "liveshow":
                self.shower = LiveShow(selected_show, self.canvas,
                                       self.showlist, self.pp_home,
                                       self.pp_profile)
                self.shower.play(self.end_shower, top=False, command='nil')

            elif selected_show['type'] == "menu":
                self.shower = MenuShow(selected_show, self.canvas,
                                       self.showlist, self.pp_home,
                                       self.pp_profile)
                self.shower.play(self.end_shower, top=False, command='nil')

            else:
                self.mon.err(self,
                             "Unknown Show Type: " + selected_show['type'])
                self._end('error' "Unknown show type")

        else:
            self.mon.err(self, "Unknown Track Type: " + track_type)
            self._end('error', "Unknown track type")

    def ready_callback(self):
        self._delete_eggtimer()

    def end_player(self, reason, message):
        self._req_next = 'nil'
        self.mon.log(self, "Returned from player with message: " + message)
        self.player = None
        if reason in ("killed", "error"):
            self._end(reason, message)
        elif self.show['progress'] == "manual":
            self._display_eggtimer(self.resource('mediashow', 'm05'))
            self._req_next = reason
            self._what_next()
        else:
            self._req_next = reason
            self._what_next()

    def end_shower(self, reason, message):
        self._req_next = 'nil'
        self.mon.log(self, "Returned from shower with message: " + message)
        self.shower = None
        if reason in ("killed", "error"):
            self._end(reason, message)
        elif self.show['progress'] == "manual":
            self._display_eggtimer(self.resource('mediashow', 'm06'))
            self._req_next = reason
            self._what_next()
        else:
            self._req_next = reason
            self._what_next()

    def _display_eggtimer(self, text):
        self.canvas.create_text(int(self.canvas['width']) / 2,
                                int(self.canvas['height']) / 2,
                                text=text,
                                fill='white',
                                font="Helvetica 20 bold")
        self.canvas.update_idletasks()

    def _delete_eggtimer(self):
        self.canvas.delete(ALL)
Example #8
0
class LiveShow:


    NEW_TRACKS={'image':{'title':'New Image','track-ref':'','type':'image','location':'','duration':'','transition':'',
                              'track-text':'','track-text-font':'','track-text-colour':'','track-text-x':'0','track-text-y':'0'},
                'video':{'title':'New Video','track-ref':'','type':'video','location':'','omx-audio':''}}
    
    IMAGE_FILES=('.gif','.jpg','.jpeg','.bmp','.png','.tif')
    VIDEO_FILES=('.mp4','.mkv','.avi','.mp2','.wmv', '.vob')
    AUDIO_FILES=('.mp3','.wav','.ogg')
                         
# *******************
# External interface
# ********************

    def __init__(self,
                            show,
                            canvas,
                            showlist,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the show is to be written on
            showlist - used jus to check the issue of medialist against showlist
            show - the dictionary for the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show =show
        self.showlist=showlist
        self.canvas=canvas
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()

        # Init variables
        self.player=None
        self.shower=None
        self._end_liveshow_signal=False
        self._play_child_signal = False
        self.error=False
        
        self._livelist=None
        self._new_livelist= None


    def play(self,end_callback,ready_callback=None, top=False,command='nil'):

        """ displays the liveshow
              end_callback - function to be called when the liveshow exits
              ready_callback - callback when liveshow is ready to display
              top is True when the show is top level (i.e. run from start show)
        """

        #instantiate the arguments
        self._end_callback=end_callback
        self._ready_callback=ready_callback
        self.top=top
        self.mon.log(self,"Starting show: " + self.show['show-ref'])

        # check  data files are available.
        self.media_file = self.pp_profile + os.sep + self.show['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self,"Medialist file not found: "+ self.media_file)
            self._stop("Medialist file not found")
            
        self.options=command_options()
               
        self._pp_live_dir1 = self.pp_home + os.sep + 'pp_live_tracks'
        if not os.path.exists(self._pp_live_dir1):
            os.mkdir(self._pp_live_dir1)

        self._pp_live_dir2=''   
        if self.options['liveshow'] <>"":
            self._pp_live_dir2 = self.options['liveshow']
            if not os.path.exists(self._pp_live_dir2):
                self.mon.err(self,"live tracks directory not found " + self._pp_live_dir2)
                self._end('error',"live tracks directory not found")

        #create a medialist for the liveshow and read it.
        # it should be empty of anonymous tracks but read it to check its version.
        self.medialist=MediaList()
        if self.medialist.open_list(self.media_file,self.showlist.sissue())==False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self._end('error',"Version of medialist different to Pi Presents")
        
        if self.ready_callback<>None:
             self.ready_callback()
             
        self._play_first_track()
        
 


   # respond to key presses.
    def key_pressed(self,key_name):
        self.mon.log(self,"received key: " + key_name)
        
        if key_name=='':
            pass
        
        elif key_name=='escape':
            # if next lower show eor player is running pass down to stop the show/track
            # ELSE stop this show except for exceptions
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            elif self.player<>None:
                self.player.key_pressed(key_name)
            else:
                # not at top so stop the show
                if  self.top == False:
                    self._stop("exit show to higher level")
                else:
                    pass
    
        elif key_name in ('up','down'):
        # if child or sub-show is running and is a show pass to show, track does not use up/down
            if self.shower<>None:
                self.shower.key_pressed(key_name)

                
        elif key_name=='return':
            # if child show or sub-show is running and is show - pass down
            # ELSE use Return to start child
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            else:
                if self.show['has-child']=="yes":
                    self._play_child()
              
        elif key_name in ('p',' '):
            # pass down if show or track running.
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            elif self.player<>None:
                self.player.key_pressed(key_name)
 

    def button_pressed(self,button,edge):
        if button=='play': self.key_pressed("return")
        elif  button =='up': self.key_pressed("up")
        elif button=='down': self.key_pressed("down")
        elif button=='stop': self.key_pressed("escape")
        elif button=='pause': self.key_pressed('p')


       
    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.mon.log(self,"sent terminate to shower")
            self.shower.terminate(reason)
        elif self.player<>None:
            self.mon.log(self,"sent terminate to player")
            self.player.terminate(reason)
        else:
            self._end(reason,'terminated without terminating shower or player')

 
    def _tidy_up(self):
        pass


    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error",'Cannot find resource')
        else:
            return value
        

# ***************************
# Respond to key/button presses
# ***************************

    def _stop(self,message):
        self._end_liveshow_signal=True

        
    def _play_child(self):
        self._play_child_signal=True
        if self.player<>None:
            self.player.key_pressed("escape")
      
        
# ***************************
# end of show functions
# ***************************

    def _end(self,reason,message):
        self._end_liveshow_signal=False
        self.mon.log(self,"Ending Liveshow: "+ self.show['show-ref'])
        self._tidy_up()
        self._end_callback(reason,message)
        self=None
        return
        
    def _nend(self):
        self._end('normal','end from state machine')
  

# ***************************
# Livelist
# ***************************       
        
    def _livelist_add_track(self,afile):
        (root,title)=os.path.split(afile)
        (root,ext)= os.path.splitext(afile)
        if ext.lower() in LiveShow.IMAGE_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['image'],{'title':title,'track-ref':'','location':afile})
        if ext.lower() in LiveShow.VIDEO_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['video'],{'title':title,'track-ref':'','location':afile})
        if ext.lower() in LiveShow.AUDIO_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['video'],{'title':title,'track-ref':'','location':afile})
           


        
    def _livelist_new_track(self,fields,values):
        new_track=fields
        self._new_livelist.append(copy.deepcopy(new_track))
        last = len(self._new_livelist)-1
        self._new_livelist[last].update(values)        
    

        
    def _new_livelist_create(self):
     
        self._new_livelist=[]
        if os.path.exists(self._pp_live_dir1):
            for file in os.listdir(self._pp_live_dir1):
                file = self._pp_live_dir1 + os.sep + file
                (root_file,ext_file)= os.path.splitext(file)
                if ext_file.lower() in LiveShow.IMAGE_FILES+LiveShow.VIDEO_FILES+LiveShow.AUDIO_FILES:
                    self._livelist_add_track(file)
                    
        if os.path.exists(self._pp_live_dir2):
            for file in os.listdir(self._pp_live_dir2):
                file = self._pp_live_dir2 + os.sep + file
                (root_file,ext_file)= os.path.splitext(file)
                if ext_file.lower() in LiveShow.IMAGE_FILES+LiveShow.VIDEO_FILES+LiveShow.AUDIO_FILES:
                    self._livelist_add_track(file)
                    

        self._new_livelist= sorted(self._new_livelist, key= lambda track: os.path.basename(track['location']).lower())
#       for it in self._new_livelist:
#          print it['location']
#      print ''


    
    def _livelist_replace_if_changed(self):
        self._new_livelist_create()
        if  self._new_livelist<>self._livelist:
            self._livelist=copy.deepcopy(self._new_livelist)
            self._livelist_index=0
   
   
    def _livelist_next(self):
        if self._livelist_index== len(self._livelist)-1:
            self._livelist_index=0
        else:
            self._livelist_index +=1


# ***************************
# Play Loop
# ***************************
 
    def _play_first_track(self):
        self._new_livelist_create()
        self._livelist = copy.deepcopy(self._new_livelist)
        self._livelist_index = 0
        self._play_track()

        
    def _play_track(self):        
        self._livelist_replace_if_changed()
        if len(self._livelist)>0:
            self._play_selected_track(self._livelist[self._livelist_index])
        else:
            self.display_message(self.canvas,None,self.resource('liveshow','m01'),5,self._what_next)
     
    def _what_next(self):   
        # user wants to end 
        if self._end_liveshow_signal==True:
            self._end_liveshow_signal=False
            self._end('normal',"show ended by user")
        
        # play child?
        elif self._play_child_signal == True:
            self._play_child_signal=False
            index = self.medialist.index_of_track('pp-child-show')
            if index >=0:
                #don't select the track as need to preserve mediashow sequence.
                child_track=self.medialist.track(index)
                self._display_eggtimer(self.resource('liveshow','m02'))
                self._play_selected_track(child_track)
            else:
                self.mon.err(self,"Child show not found in medialist: "+ self.show['pp-child-show'])
                self._end('error',"child show not found in medialist")
                
        # otherwise loop to next track                       
        else:
            self._livelist_next()
            self._play_track()
          
      
# ***************************
# Dispatching to Players/Shows 
# ***************************

    # used to display internal messages in situations where a medialist entry could not be used.
    def display_message(self,canvas,source,content,duration,_display_message_callback):
            self._display_message_callback=_display_message_callback
            tp={'duration':duration,'message-colour':'white','message-font':'Helvetica 20 bold'}
            self.player=MessagePlayer(canvas,tp,tp)
            self.player.play(content,self._display_message_end,None)

            
    def  _display_message_end(self,reason,message):
        self.player=None
        if reason in ("killed",'error'):
            self._end(reason,message)
        else:
            self._display_message_callback()


    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to play is: "+ track_file)
        return track_file     
         

    def _play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected_track is a dictionary for the track/show
        """
        # self.canvas.delete(ALL)
        
        # is menu required
        if self.show['has-child']=="yes":
            enable_child=True
        else:
            enable_child=False

        #dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
                                      
        if track_type=="image":
            track_file=self.complete_path(selected_track)
            # images played from menus don't have children
            self.player=ImagePlayer(self.canvas,self.show,selected_track)
            self.player.play(track_file,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=enable_child)
        elif track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.canvas,self.show,selected_track)
            self.player.play(track_file,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=enable_child)
            
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self._stop("Unknown show")
                
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.end_shower,top=False,command='nil')

            
            elif selected_show['type']=="menu":
                self.shower= MenuShow(selected_show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.end_shower,top=False,command='nil')
                
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self._stop("Unknown show type")  
                                                                            
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self._stop("Unknown track type")            


    def ready_callback(self):
        self._delete_eggtimer()
        
        
    def end_player(self,reason,message):
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        if reason in("killed","error"):
            self._end(reason,message)
        else:
            self._what_next()

    def end_shower(self,reason,message):
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        if reason in("killed","error"):
            self._end(reason,message)
        else:
            self._what_next()  
        
        
    def _display_eggtimer(self,text):
        self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold")
        self.canvas.update_idletasks( )


    def _delete_eggtimer(self):
            self.canvas.delete(ALL)
Example #9
0
class PiPresents(object):

    def pipresents_version(self):
        vitems=self.pipresents_issue.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])


    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3.2"
        self.pipresents_minorissue = '1.3.2a'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner


        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit(102)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'GapShow','Show','ArtShow',
                            'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player',
                            'MediaList','LiveList','ShowList',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver',
                            'Network','Mailer'
                            ]
        

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as file:
            self.mon.log(self,'\nRaspbian: '+file.read())

        self.mon.log(self,'\n'+check_output(["omxplayer", "-v"]))
        self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"]))
        
        if "DESKTOP_SESSION" not in os.environ:
            print 'Pi Presents must be run from the Desktop'
            self.mon.log(self,'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION'])
        
        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
        self.email_enabled=False


        if os.geteuid() == 0:
            self.mon.err(self,'Do not run Pi Presents with sudo')
            self.end('error','Do not run Pi Presents with sudo')

        
        user=os.getenv('USER')

        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work



        # check network is available
        self.network_connected=False
        self.network_details=False
        self.interface=''
        self.ip=''
        self.unit=''
        
        # sets self.network_connected and self.network_details
        self.init_network()

        
        # start the mailer and send email when PP starts
        self.email_enabled=False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M")
                message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + '   Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip 
                self.send_email('start',subject,message) 

         
        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.mon.err(self,"Profile not specified in command ")
            self.end('error','Profile not specified with the commands -p option')
        
       # get directory containing pp_home from the command,
        if self.options['home']  == "":
            home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)


        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found is True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self,"Failed to find pp_home directory at " + home)
            self.end('error',"Failed to find pp_home directory at " + home)


        # check profile exists
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self,"Running profile: " + self.pp_profile_path)
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error',"Failed to find requested profile: "+ self.pp_profile)

        self.mon.start_stats(self.options['profile'])
        
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

         
        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error',"showlist not found at "+self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")
            self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error',"Show [start] not found in showlist")


# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.starter_show['background-colour'])

        self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvas cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.starter_show['background-colour'])


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find, or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find, or error in screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.terminate_required=False
        self.exitpipresents_required=False

        # delete omxplayer dbus files
        # if os.path.exists("/tmp/omxplayerdbus.{}".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}".format(user))
        # if os.path.exists("/tmp/omxplayerdbus.{}.pid".format(user)):
            # os.remove("/tmp/omxplayerdbus.{}.pid".format(user))
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
                self.oscdriver=OSCDriver()
                reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end('error',message)
                else:
                    self.osc_enabled=True
                    self.root.after(1000,self.oscdriver.start_server())

        
        # enable ToD scheduler if schedule exists      
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):                
            self.tod_enabled = True
        else:
            self.tod_enabled=False

        # warn if the network not available when ToD required

        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(self,'Network not connected  so Time of Day scheduler may be using the internal clock')

        # warn about start shows and scheduler

        if self.starter_show['start-show']=='' and self.tod_enabled is False:
            self.mon.sched(self,"No Start Shows in Start Show and no shows scheduled") 
            self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] !='' and self.tod_enabled is True:
            self.mon.sched(self,"Start Shows in Start Show and shows scheduled - conflict?") 
            self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()           

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod.poll()            


        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --screensize comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in --screensize',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************
    # handles one command provided as a line of text
    
    def handle_command(self,command_text,source='',show=''):
        # print 'PIPRESENTS ',command_text,source,'from',show
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        if command_text[0]=='/': 
            if self.osc_enabled is True:
                self.oscdriver.send_command(command_text)
            return
        
        fields= command_text.split()
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''

        if show_command in ('open','close'):
            self.mon.sched(self, command_text + ' received from show:'+show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
        elif show_command =='monitor':
            self.handle_monitor_command(show_ref)
            return
        elif show_command == 'event':
            self.handle_input_event(show_ref,'Show Control')
            return
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return


    def handle_monitor_command(self,command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')           
                      
    

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal','no shows running')

    def e_shutdown_pressed(self):
        self.shutdown_pressed('now')


    def e_osc_handle_output_event(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg))

    def  osc_handle_output_event(self,line):
        self.mon.log(self,"output event received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

               
    def handle_output_event(self,symbol,param_type,param_values,req_time):
        if self.gpio_enabled is True:
            reason,message=self.gpiodriver.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)
        else:
            self.mon.warn(self,'GPIO not enabled')


    # all input events call this callback with a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # events for shows affect the show and could cause it to exit.
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()           


    def on_shutdown_delay(self):
        # 5 second delay is up, if shutdown button still pressed then shutdown
        if self.gpiodriver.shutdown_pressed() is True:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()


    def handle_sigterm(self,signum,frame):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required=True
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.starter_show['background-colour'])
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        # gc.collect()
        # print gc.garbage
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message)
            self.mon.sched(self, "Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")
                          
            # close logging files 
            self.mon.finish()
            sys.exit(101)
                          
        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message_text)   
            self.mon.sched(self, "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")
                          
            # close logging files 
            self.mon.finish()
            sys.exit(102)

        else:           
            self.mon.sched(self,"Pi Presents  exiting normally, bye\n")
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            
            # close logging files 
            self.mon.finish()
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call (['sudo','shutdown','now','SHUTTING DOWN'])
            sys.exit(100)



    def init_network(self):

        timeout=int(self.options['nonetwork'])
        if timeout== 0:
            self.network_connected=False
            self.unit=''
            self.ip=''
            self.interface=''
            return
        
        self.network=Network()
        self.network_connected=False

        # try to connect to network
        self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network')
        success=self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected=True
        self.mon.sched (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details=False
        network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit=self.network.unit

        # get interface and IP details of preferred interface
        self.interface,self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected=False
            return
        self.network_details=True
        self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip)


    def init_mailer(self):

        self.email_enabled=False
        email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self,'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self,'Found pp_email.cfg at ' + email_file_path)
        self.mailer=Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled=True
            self.mon.log (self,'Email Enabled')



    def send_email(self,reason,subject,message):
        if self.try_connect() is False:
            return False
        else:
            success,error = self.mailer.send(subject,message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self,'Sent email for ' + reason)
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect from email server ' + str(error))
                return True


    def try_connect(self):
        tries=1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) +  '\n ' +str(error))
                tries +=1
                if tries >5:
                    self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries))
                    return False

                
    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation and gpio
        if self.animate is not None:
            self.animate.terminate()
            
        if self.gpio_enabled==True:
            self.gpiodriver.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()
Example #10
0
class MediaList(object):
    """
    manages a media list of tracks and the track selected from the medialist
    """
    def __init__(self, sequence):
        self.clear()
        self.mon = Monitor()
        self.sequence = sequence

# Functions for the editor dealing with complete list

    def clear(self):
        self._tracks = []  #MediaList, stored as a list of dicts
        self._num_tracks = 0
        self._selected_track_index = -1  # index of currently selected track

    def print_list(self):
        print '\n'
        print self._tracks

    def first(self):
        self._selected_track_index = -1
        self.next(self.sequence
                  )  #let this do the work of randomaising or  advancing to 0

    def length(self):
        return self._num_tracks

    def append(self, track_dict):
        # print '\ntrack dict',track_dict
        """appends a track dictionary to the end of the medialist store"""
        self._tracks.append(copy.deepcopy(track_dict))
        self._num_tracks += 1

    def update(self, index, values):
        self._tracks[index].update(values)

    def remove(self, index):
        self._tracks.pop(index)
        self._num_tracks -= 1
        # deselect any track, saves worrying about whether index needs changing
        self._selected_track_index = -1

    def move_up(self):
        if self._selected_track_index != 0:
            self._tracks.insert(self._selected_track_index - 1,
                                self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index - 1)

    def move_down(self):
        if self._selected_track_index != self._num_tracks - 1:
            self._tracks.insert(self._selected_track_index + 1,
                                self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index + 1)

    def replace(self, index, replacement):
        self._tracks[index] = replacement

# Common functions work for anything

    def track_is_selected(self):
        if self._selected_track_index >= 0:
            return True
        else:
            return False

    def selected_track_index(self):
        return self._selected_track_index

    def track(self, index):
        return self._tracks[index]

    def selected_track(self):
        """returns a dictionary containing all fields in the selected track """
        return self._selected_track

    def select(self, index):
        """does housekeeping necessary when a track is selected"""
        if self._num_tracks > 0 and index >= 0 and index < self._num_tracks:
            self._selected_track_index = index
            self._selected_track = self._tracks[index]
            return True
        else:
            return False

# Dealing with anonymous tracks for use and display

    def at_end(self):
        # true is selected track is last anon
        index = self._num_tracks - 1
        while index >= 0:
            if self._tracks[index]['track-ref'] == "":
                end = index
                if self._selected_track_index == end:
                    return True
                else:
                    return False
            index -= 1
        return False

    def index_of_end(self):
        if self.anon_length() == 0:
            return False
        index = self._num_tracks - 1
        while index >= 0:
            if self._tracks[index]['track-ref'] == "":
                return index
            index -= 1
        return -1

    def at_start(self):
        if self.anon_length() == 0:
            return False
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                start = index
                if self._selected_track_index == start:
                    return True
                else:
                    return False
            index += 1
        return False

    def index_of_start(self):
        if self.anon_length() == 0:
            return False
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                return index
            index += 1
        return False

    def anon_length(self):
        # number of anonymous tracks
        count = 0
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                count += 1
            index += 1
        return count

    def start(self):
        if self.anon_length() == 0:
            return False
        # select first anonymous track in the list
        if self.sequence == 'ordered':
            index = 0
            while index < self._num_tracks:
                if self._tracks[index]['track-ref'] == "":
                    self.select(index)
                    return True
                index += 1
            return False
        else:
            match = random.randint(0, self.anon_length() - 1)
            # print 'match',match
            index = 0
            while index < self._num_tracks:
                if self._tracks[index]['track-ref'] == "" and index == match:
                    self.select(index)
                    # print index
                    return index
                index += 1

    def finish(self):
        if self.anon_length() == 0:
            return False
        if self.sequence == 'ordered':
            # select last anymous track in the list
            index = self._num_tracks - 1
            while index >= 0:
                if self._tracks[index]['track-ref'] == "":
                    self.select(index)
                    return True
                index -= 1
            return False
        else:
            match = random.randint(0, self.anon_length() - 1)
            # print 'match',match
            index = 0
            while index < self._num_tracks:
                if self._tracks[index]['track-ref'] == "" and index == match:
                    self.select(index)
                    # print index
                    return index
                index += 1

    def select_anon_by_index(self, wanted):
        if self.anon_length() == 0:
            return False
        index = 0
        anon_index = 0
        while index != self._num_tracks:
            # print index,self._tracks[index] ['track-ref'],wanted
            if self._tracks[index]['track-ref'] == "":
                if anon_index == wanted:
                    # print 'match\n'
                    self.select(index)
                    return True
                anon_index += 1
            index = index + 1
        return False

    def next(self, sequence):
        if self.anon_length() == 0:
            return False
        if sequence == 'ordered':
            if self._selected_track_index == self._num_tracks - 1:
                index = 0
            else:
                index = self._selected_track_index + 1

            end = self._selected_track_index
        else:
            index = random.randint(0, self.anon_length() - 1)
            if index == 0:
                end = self._num_tracks - 1
            else:
                end = index - 1
        # search for next anonymous track
        # print 'index', index, 'end',end
        while index != end:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            if index == self._num_tracks - 1:
                index = 0
            else:
                index = index + 1
        return False

    def previous(self, sequence):
        if self.anon_length() == 0:
            return False
        if sequence == 'ordered':
            if self._selected_track_index == 0:
                index = self._num_tracks - 1
            else:
                index = self._selected_track_index - 1
            end = self._selected_track_index
        else:
            index = random.randint(0, self.anon_length() - 1)
            if index == self._num_tracks - 1:
                end = 0
            else:
                end = index + 1
        # print 'index', index, 'end',end
        # search for previous anonymous track
        while index != end:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            if index == 0:
                index = self._num_tracks - 1
            else:
                index = index - 1
        return False

# Lookup for labelled tracks

    def index_of_track(self, wanted_track):
        index = 0
        for track in self._tracks:
            if track['track-ref'] == wanted_track:
                return index
            index += 1
        return -1

# open and save

    def open_list(self, filename, profile_version):
        """
        opens a saved medialist
        medialists are stored as json arrays.
        """
        ifile = open(filename, 'rb')
        mdict = json.load(ifile)
        ifile.close()
        self._tracks = mdict['tracks']
        if 'issue' in mdict:
            self.medialist_version_string = mdict['issue']
        else:
            self.medialist_version_string = "1.0"

        if self.medialist_version() == profile_version:
            self._num_tracks = len(self._tracks)
            self.last_num_tracks = self._num_tracks
            self._selected_track_index = -1
            return True
        else:
            return False

    def medialist_version(self):
        vitems = self.medialist_version_string.split('.')
        if len(vitems) == 2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000 * int(vitems[0]) + 100 * int(vitems[1])
        else:
            return 1000 * int(vitems[0]) + 100 * int(vitems[1]) + int(
                vitems[2])

    # dummy for mediliast, in livelist the list is created from the live_track directories
    def use_new_livelist(self):
        pass

    def create_new_livelist(self):
        pass

    def new_length(self):
        return self.length()

    # for medialist the content of the list never changes so return False
    def livelist_changed(self):
        return False

    def save_list(self, filename):
        """ save a medialist """
        if filename == "":
            return False
        dic = {'issue': self.medialist_version_string, 'tracks': self._tracks}
        filename = str(filename)
        filename = string.replace(filename, '\\', '/')
        tries = 1
        while tries <= 10:
            # print "save  medialist  ",filename
            try:
                ofile = open(filename, "wb")
                json.dump(dic, ofile, sort_keys=True, indent=1)
                ofile.close()
                self.mon.log(self, "Saved medialist " + filename)
                break
            except IOError:
                self.mon.err(
                    self,
                    "failed to save medialist, trying again " + str(tries))
                tries += 1
        return
Example #11
0
class OSCRemote(object):
    def __init__(self):

        self.editor_issue = "1.3"

        # get command options
        self.command_options = remote_options()

        # get directory holding the code
        self.pp_dir = sys.path[0]

        if not os.path.exists(self.pp_dir + os.sep + "pp_oscremote.py"):
            tkMessageBox.showwarning("Pi Presents",
                                     "Bad Application Directory")
            exit()

        # Initialise logging
        Monitor.log_path = self.pp_dir
        self.mon = Monitor()
        self.mon.init()

        Monitor.classes = ['OSCRemote', 'OSCConfig', 'OSCEditor']

        Monitor.log_level = int(self.command_options['debug'])

        self.mon.log(self, "Pi Presents Remote is starting")
        self.mon.log(self, " OS and separator " + os.name + '  ' + os.sep)
        self.mon.log(self,
                     "sys.path[0] -  location of code: code " + sys.path[0])

        self.setup_gui()

        # OSC config class
        self.osc_config = OSCConfig()

        self.init()

        #and start the system
        self.root.after(1000, self.run_app)
        self.root.mainloop()

    def init(self):
        self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscremote.cfg'
        self.read_create_osc()
        self.pp_home_dir = self.osc_config.pp_home_dir
        self.pp_profiles_offset = self.osc_config.pp_profiles_offset
        self.mon.log(self, "Data Home from options is " + self.pp_home_dir)
        self.mon.log(
            self, "Current Profiles Offset from options is " +
            self.pp_profiles_offset)
        self.pp_profile_dir = ''

        self.current_showlist = None
        self.current_show = None
        self.current_show_ref = ''
        self.shows_display.delete(0, END)
        self.results.set('')

    def add_status(self, text):
        self.status_display.insert(END, text + '\n')
        self.status_display.see(END)

    def run_app(self):
        self.client = None
        self.server = None
        self.st = None

        # initialise OSC variables
        self.prefix = '/pipresents'
        self.this_unit = '/' + self.osc_config.this_unit_name
        self.add_status('this unit is: ' + self.this_unit)
        self.controlled_unit = '/' + self.osc_config.controlled_unit_1_name
        self.add_status('controlled unit is: ' + self.controlled_unit)

        #connect client then start server to listen for replies
        self.init_client()
        self.add_status('connecting to controlled unit: ' +
                        self.osc_config.controlled_unit_1_ip + ':' +
                        self.osc_config.controlled_unit_1_port + ' ' +
                        self.osc_config.controlled_unit_1_name)
        self.connect_client(self.osc_config.controlled_unit_1_ip,
                            self.osc_config.controlled_unit_1_port)
        self.add_status('listening for replies on:' +
                        self.osc_config.this_unit_ip + ':' +
                        self.osc_config.this_unit_port)
        self.init_server(self.osc_config.this_unit_ip,
                         self.osc_config.this_unit_port, self.client)
        self.add_initial_handlers()
        self.start_server()

# ***************************************
#  RESPOND TO BUTTONS
# ***************************************

    def open_show(self):
        self.msg_path = '/core/open '
        self.msg_arg_text = self.current_show_ref
        self.display_msg_text()

    def close_show(self):
        self.msg_path = '/core/close '
        self.msg_arg_text = self.current_show_ref
        self.display_msg_text()

    def exit_pipresents(self):
        self.msg_path = '/core/exitpipresents'
        self.msg_arg_text = ''
        self.display_msg_text()

    def play_event(self):
        self.msg_path = '/core/event'
        self.msg_arg_text = 'pp-play'
        self.display_msg_text()

    def pause_event(self):
        self.msg_path = '/core/event'
        self.msg_arg_text = 'pp-pause'
        self.display_msg_text()
        pass

    def stop_event(self):
        self.msg_path = '/core/event'
        self.msg_arg_text = 'pp-stop'
        self.display_msg_text()
        pass

    def up_event(self):
        self.msg_path = '/core/event'
        self.msg_arg_text = 'pp-up'
        self.display_msg_text()

    def down_event(self):
        self.msg_path = '/core/event'
        self.msg_arg_text = 'pp-down'
        self.display_msg_text()

    def output(self):
        self.msg_path = '/core/output'
        self.msg_arg_text = ''
        self.display_msg_text()

    def loopback(self):
        self.msg_path = '/system/loopback'
        self.msg_arg_text = ''
        self.display_msg_text()

    def server_info(self):
        self.msg_path = '/system/server-info'
        self.msg_arg_text = ''
        self.display_msg_text()

    # and put the created text in the results box in the gui
    def display_msg_text(self):
        self.results.set(self.prefix + self.controlled_unit + self.msg_path +
                         ' ' + self.msg_arg_text)

    #calback from the Send button
    # parses the message string into fields and sends - NO error checking
    def send_message(self):
        msg_text = self.results.get()
        self.add_status('Send message:' + msg_text)
        self.mon.log(self, 'send message: ' + msg_text)
        fields = msg_text.split()
        address = fields[0]
        arg_list = fields[1:]
        self.send(address, arg_list)

    # ***************************************
    # OSC CLIENT TO SEND MESSAGES
    # ***************************************

    def init_client(self):
        self.client = OSC.OSCClient()

    def connect_client(self, ip, port):
        self.mon.log(self, 'connect to: ' + ip + ':' + str(port))
        self.client.connect((ip, int(port)))

    def send(self, address, arg_list):
        msg = OSC.OSCMessage()
        msg.setAddress(address)
        for arg in arg_list:
            msg.append(arg)
        self.client.send(msg)

    def disconnect_client(self):
        self.client.close()
        return

    # ***************************************
    # OSC SERVER TO LISTEN TO REPLIES
    # ***************************************

    def init_server(self, ip, port_text, client):
        self.mon.log(self, 'Start Server: ' + ip + ':' + port_text)
        self.server = OSC.OSCServer((ip, int(port_text)), client)

    def start_server(self):
        self.st = threading.Thread(target=self.server.serve_forever)
        self.st.start()

    def close_server(self):
        if self.server != None:
            self.server.close()
        self.mon.log(self, 'Waiting for Server-thread to finish')
        if self.st != None:
            self.st.join()  ##!!!
        self.mon.log(self, 'server thread closed')

    def add_initial_handlers(self):
        self.server.addMsgHandler('default', self.no_match_handler)
        self.server.addMsgHandler(
            self.prefix + self.this_unit + "/system/loopback-reply",
            self.loopback_reply_handler)
        self.server.addMsgHandler(
            self.prefix + self.this_unit + "/system/server-info-reply",
            self.server_info_reply_handler)

    def no_match_handler(self, addr, tags, stuff, source):
        text = ''
        text += "no match for new osc msg from %s" % OSC.getUrlStr(
            source) + '\n'
        text += "with addr : %s" % addr + '\n'
        text += "typetags %s" % tags + '\n'
        text += "data %s" % stuff + '\n'
        self.add_status(text + '\n')

    def loopback_reply_handler(self, addr, tags, stuff, source):
        self.add_status('Loopback reply  received from: ' +
                        self.pretty_list(source))

    def server_info_reply_handler(self, addr, tags, stuff, source):
        self.add_status('Server Information from: ' +
                        self.pretty_list(source) + '\n   ' +
                        self.pretty_list(stuff))

    def pretty_list(self, fields):
        text = ' '
        for field in fields:
            text += str(field) + ' '
        return text

    # ***************************************
    # INIT EXIT MISC
    # ***************************************

    def e_edit_osc(self):
        self.disconnect_client()
        self.close_server()
        self.edit_osc()
        self.init()
        self.add_status('\n\n\nRESTART')
        self.run_app()

    def app_exit(self):
        self.disconnect_client()
        self.close_server()
        if self.root is not None:
            self.root.destroy()
        self.mon.finish()
        sys.exit()

    def show_help(self):
        tkMessageBox.showinfo("Help", "Read 'manual.pdf'")

    def about(self):
        tkMessageBox.showinfo(
            "About", "Simple Remote Control for Pi Presents\n" +
            "Author: Ken Thompson" +
            "\nWebsite: http://pipresents.wordpress.com/")

    def setup_gui(self):
        # set up the gui

        # root is the Tkinter root widget
        self.root = Tk()
        self.root.title("Remote Control for Pi Presents")

        # self.root.configure(background='grey')

        self.root.resizable(False, False)

        # define response to main window closing
        self.root.protocol("WM_DELETE_WINDOW", self.app_exit)

        # bind some display fields
        self.filename = StringVar()
        self.display_show = StringVar()
        self.results = StringVar()
        self.status = StringVar()

        # define menu
        menubar = Menu(self.root)

        profilemenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        profilemenu.add_command(label='Select',
                                command=self.open_existing_profile)
        menubar.add_cascade(label='Profile', menu=profilemenu)

        toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Tools', menu=toolsmenu)

        osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='OSC', menu=osc_configmenu)
        osc_configmenu.add_command(label='Edit', command=self.e_edit_osc)

        helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Help', menu=helpmenu)
        helpmenu.add_command(label='Help', command=self.show_help)
        helpmenu.add_command(label='About', command=self.about)

        self.root.config(menu=menubar)

        #top frame
        top_frame = Frame(self.root, padx=5, pady=5)
        top_frame.pack(side=TOP)

        results_label = Label(top_frame,
                              text="Message to Send",
                              font="arial 12 bold")
        results_label.pack(side=LEFT)
        results_display = Entry(top_frame, textvariable=self.results, width=70)
        results_display.pack(side=LEFT, fill=BOTH, expand=1)
        send_button = Button(top_frame,
                             width=5,
                             height=1,
                             text='Send',
                             fg='black',
                             command=self.send_message,
                             bg="light grey")
        send_button.pack(side=RIGHT)

        #bottom frame
        bottom_frame = Frame(self.root, padx=5, pady=5)
        bottom_frame.pack(side=TOP, fill=BOTH, expand=1)
        left_frame = Frame(bottom_frame, padx=5)
        left_frame.pack(side=LEFT)
        right_frame = Frame(bottom_frame, padx=5, pady=5)
        right_frame.pack(side=LEFT)

        suplabel_frame = Frame(right_frame, pady=5)
        suplabel_frame.pack(side=TOP)
        commands_label = Label(suplabel_frame,
                               text="Show Control",
                               font="arial 12 bold")
        commands_label.pack()

        supervisor_frame = Frame(right_frame, pady=5)
        supervisor_frame.pack(side=TOP)

        # supervisor buttons
        add_button = Button(supervisor_frame,
                            width=5,
                            height=1,
                            text='Open\nShow',
                            fg='black',
                            command=self.open_show,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(supervisor_frame,
                            width=5,
                            height=1,
                            text='Close\nShow',
                            fg='black',
                            command=self.close_show,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(supervisor_frame,
                            width=10,
                            height=1,
                            text='Exit\nPi Presents',
                            fg='black',
                            command=self.exit_pipresents,
                            bg="light grey")
        add_button.pack(side=LEFT)

        # events buttons
        oplabel_frame = Frame(right_frame, pady=5)
        oplabel_frame.pack(side=TOP)
        operations_label = Label(oplabel_frame,
                                 text="Input Events",
                                 font="arial 12 bold")
        operations_label.pack()

        operations_frame = Frame(right_frame, pady=5)
        operations_frame.pack(side=TOP)

        add_button = Button(operations_frame,
                            width=5,
                            height=1,
                            text='Play',
                            fg='black',
                            command=self.play_event,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(operations_frame,
                            width=5,
                            height=1,
                            text='Pause',
                            fg='black',
                            command=self.pause_event,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(operations_frame,
                            width=5,
                            height=1,
                            text='Stop',
                            fg='black',
                            command=self.stop_event,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(operations_frame,
                            width=5,
                            height=1,
                            text='Up',
                            fg='black',
                            command=self.up_event,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(operations_frame,
                            width=5,
                            height=1,
                            text='Down',
                            fg='black',
                            command=self.down_event,
                            bg="light grey")
        add_button.pack(side=LEFT)

        # animate buttons
        animate_frame = Frame(right_frame, pady=5)
        animate_frame.pack(side=TOP)
        animate_label = Label(animate_frame,
                              text="Control Outputs",
                              font="arial 12 bold")
        animate_label.pack()

        animate_frame = Frame(right_frame, pady=5)
        animate_frame.pack(side=TOP)

        add_button = Button(animate_frame,
                            width=5,
                            height=1,
                            text='Output',
                            fg='black',
                            command=self.output,
                            bg="light grey")
        add_button.pack(side=LEFT)

        # system buttons
        systemlabel_frame = Frame(right_frame, pady=5)
        systemlabel_frame.pack(side=TOP)
        system_label = Label(systemlabel_frame,
                             text="System",
                             font="arial 12 bold")
        system_label.pack()

        system_frame = Frame(right_frame, pady=5)
        system_frame.pack(side=TOP)

        add_button = Button(system_frame,
                            width=5,
                            height=1,
                            text='Loopback',
                            fg='black',
                            command=self.loopback,
                            bg="light grey")
        add_button.pack(side=LEFT)
        add_button = Button(system_frame,
                            width=10,
                            height=1,
                            text='Server Info',
                            fg='black',
                            command=self.server_info,
                            bg="light grey")
        add_button.pack(side=LEFT)

        # define display of showlist
        shows_title_frame = Frame(left_frame)
        shows_title_frame.pack(side=TOP)
        shows_label = Label(shows_title_frame, text="Shows")
        shows_label.pack()
        shows_frame = Frame(left_frame)
        shows_frame.pack(side=TOP)
        scrollbar = Scrollbar(shows_frame, orient=VERTICAL)
        self.shows_display = Listbox(shows_frame,
                                     selectmode=SINGLE,
                                     height=12,
                                     width=40,
                                     bg="white",
                                     activestyle=NONE,
                                     fg="black",
                                     yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.shows_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.shows_display.pack(side=LEFT, fill=BOTH, expand=1)
        self.shows_display.bind("<ButtonRelease-1>", self.e_select_show)

        # status_frame
        status_frame = Frame(self.root, padx=5, pady=5)
        status_frame.pack(side=TOP, fill=BOTH, expand=1)
        status_label = Label(status_frame, text="Status", font="arial 12 bold")
        status_label.pack(side=LEFT)
        scrollbar = Scrollbar(status_frame, orient=VERTICAL)
        self.status_display = Text(status_frame,
                                   height=10,
                                   yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.status_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.status_display.pack(side=LEFT, fill=BOTH, expand=1)

    # ***************************************
    # SHOWLIST
    # ***************************************

    def open_existing_profile(self):
        initial_dir = self.pp_home_dir + os.sep + "pp_profiles" + self.pp_profiles_offset
        if os.path.exists(initial_dir) is False:
            self.mon.err(
                self, "Profiles directory not found: " + initial_dir +
                "\n\nHint: Data Home option must end in pp_home")
            return
        dir_path = tkFileDialog.askdirectory(initialdir=initial_dir)
        # dir_path="C:\Users\Ken\pp_home\pp_profiles\\ttt"
        if len(dir_path) > 0:
            self.open_profile(dir_path)

    def open_profile(self, dir_path):
        showlist_file = dir_path + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file) is False:
            self.mon.err(
                self, "Not a Profile: " + dir_path +
                "\n\nHint: Have you opened the profile directory?")
            return
        self.pp_profile_dir = dir_path
        self.root.title("Remote for Pi Presents - " + self.pp_profile_dir)
        self.open_showlist(self.pp_profile_dir)

    def open_showlist(self, profile_dir):
        showlist_file = profile_dir + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file) is False:
            self.mon.err(
                self, "showlist file not found at " + profile_dir +
                "\n\nHint: Have you opened the profile directory?")
            self.app_exit()
        self.current_showlist = ShowList()
        self.current_showlist.open_json(showlist_file)
        if float(self.current_showlist.sissue()) != float(self.editor_issue):
            self.mon.err(
                self, "Version of profile does not match Remote: " +
                self.editor_issue)
            self.app_exit()
        self.refresh_shows_display()

    def refresh_shows_display(self):
        self.shows_display.delete(0, self.shows_display.size())
        for index in range(self.current_showlist.length()):
            self.shows_display.insert(
                END,
                self.current_showlist.show(index)['title'] + "   [" +
                self.current_showlist.show(index)['show-ref'] + "]")
        if self.current_showlist.show_is_selected():
            self.shows_display.itemconfig(
                self.current_showlist.selected_show_index(), fg='red')
            self.shows_display.see(self.current_showlist.selected_show_index())

    def e_select_show(self, event):
        print 'select show', self.current_showlist.length()
        if self.current_showlist is not None and self.current_showlist.length(
        ) > 0:
            mouse_item_index = int(event.widget.curselection()[0])
            self.current_showlist.select(mouse_item_index)
            self.current_show_ref = self.current_showlist.selected_show(
            )['show-ref']
            self.refresh_shows_display()
        else:
            self.current_show_ref = ''


# ***************************************
#  OSC CONFIGURATION
# ***************************************

    def read_create_osc(self):
        if self.osc_config.read(self.osc_config_file) is False:
            self.osc_config.create(self.osc_config_file)
            eosc = OSCEditor(self.root, self.osc_config_file, 'remote',
                             'Create OSC Remote Configuration')
            self.osc_config.read(self.osc_config_file)

    def edit_osc(self):
        if self.osc_config.read(self.osc_config_file) is False:
            self.osc_config.create(self.osc_config_file)
        eosc = OSCEditor(self.root, self.osc_config_file, 'remote',
                         'Edit OSC Reomote Configuration')
class PiPresents:

    def __init__(self):
        
        self.pipresents_issue="1.2"
        self.nonfull_window_width = 0.6 # proportion of width
        self.nonfull_window_height= 0.6 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner
        
        StopWatch.global_enable=False

#****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get pi presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()

        
        #Initialise logging
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        self.mon.on()
        if self.options['debug']==True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False
 
        self.mon.log (self, "Pi Presents is starting")
        self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME'))
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))

        # optional other classes used
        self.ppio=None
        self.tod=None
         
        #get profile path from -p option
        if self.options['profile']<>"":
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"
        
       #get directory containing pp_home from the command,
        if self.options['home'] =="":
            home = os.path.expanduser('~')+ os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)
        
        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home=pp_dir+"/pp_home"
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found==True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:    
            self.mon.log(self,"FAILED to find requested home directory, using default to display error message: " + self.pp_home)


        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile"   
            self.mon.log(self,"FAILED to find requested profile, using default to display error message: pp_profile")
        
        if self.options['verify']==True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False:
                tkMessageBox.showwarning("Pi Presents","Validation Failed")
                exit()
                
        # open the resources
        self.rr=ResourceReader()
        # read the file, done once for all the other classes to use.
        if self.rr.read(pp_dir,self.pp_home,self.pp_profile)==False:
            self.end('error','cannot find resources.cfg')            

        #initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error','showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue())<>float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self.end('error','wrong version of profile')
 
        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error','start show not found')

        
# ********************
# SET UP THE GUI
# ********************
        #turn off the screenblanking and saver
        if self.options['noblank']==True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')
        
        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions and decorations
        if self.options['fullscreen']==True:

            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')
        else:
            self.window_width=int(self.screen_width*self.nonfull_window_width)
            self.window_height=int(self.screen_height*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))

            
        #canvas covers the whole window
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
        
        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.exit_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height,
                                       width=self.canvas_width,
                                       highlightthickness=0)
        # self.canvas.pack()
        self.canvas.place(x=0,y=0)

        self.canvas.focus_set()

                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # looks after bindings between symbolic names and internal operations
        controlsmanager=ControlsManager()
        if controlsmanager.read(pp_dir,self.pp_home,self.pp_profile)==False:
                self.end('error','cannot find or error in controls.cfg.cfg')
        else:
            controlsmanager.parse_defaults()

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile)==False:
                self.end('error','cannot find or error in keys.cfg')
        kbd.bind_keys(self.root,self.input_pressed)

        self.sr=ScreenDriver()
        # read the screen click area config file
        if self.sr.read(pp_dir,self.pp_home,self.pp_profile)==False:
            self.end('error','cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason,message = self.sr.make_click_areas(self.canvas,self.input_pressed)
        if reason=='error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        
        #kick off GPIO if enabled by command line option
        if self.options['gpio']==True:
            from pp_gpio import PPIO
            # initialise the GPIO
            self.ppio=PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.gpio_pressed)==False:
                self.end('error','gpio error')
                
            # and start polling gpio
            self.ppio.poll()

        #kick off the time of day scheduler
        self.tod=TimeOfDay()
        self.tod.init(pp_dir,self.pp_home,self.canvas,500)
        self.tod.poll()


        # Create list of start shows initialise them and then run them
        self.run_start_shows()

        #start tkinter
        self.root.mainloop( )



# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        #start show manager
        show_id=-1 #start show
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        
        #first time through so empty show register and set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback)

        #parse the start shows field and start the initial shows       
        start_shows_text=self.starter_show['start-show']
        self.show_manager.start_initial_shows(start_shows_text)

    #callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message,force_shutdown):
        self.mon.log(self,"All shows ended, so terminate Pi Presents")
        if force_shutdown==True:
            self.shutdown_required=True
            self.mon.log(self,"shutdown forced by profile")  
            self.terminate('killed')
        else:
            self.end(reason,message)


# *********************
# User inputs
# ********************

    #gpio callback - symbol provided by gpio
    def gpio_pressed(self,index,symbol,edge):
        self.mon.log(self, "GPIO Pressed: "+ symbol)
        self.input_pressed(symbol,edge,'gpio')


    
    # all input events call this callback with a symbolic name.              
    def input_pressed(self,symbol,edge,source):
        self.mon.log(self,"input received: "+symbol)
        if symbol=='pp-exit':
            self.exit_pressed()
        elif symbol=='pp-shutdown':
            self.shutdown_pressed('delay')
        elif symbol=='pp-shutdownnow':
            self.shutdown_pressed('now')
        else:
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj<>None:
                    show_obj.input_pressed(symbol,edge,source)


# **************************************
# respond to exit inputs by terminating
# **************************************

    def shutdown_pressed(self, when):
        if when=='delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            self.exit_pressed()           

    def on_shutdown_delay(self):
        if self.ppio.shutdown_pressed():
            self.shutdown_required=True
            self.exit_pressed()

         
    def exit_pressed(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players     
        self.mon.log(self,"kill sent to shows")   
        self.terminate('killed')


     # kill or error
    def terminate(self,reason):
        needs_termination=False
        for show in self.show_manager.shows:
            if show[ShowManager.SHOW_OBJ]<>None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                show[ShowManager.SHOW_OBJ].terminate(reason)
        if needs_termination==False:
            self.end(reason,'terminate - no termination of lower levels required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with message: " + reason + ' ' + message)
        if reason=='error':
            self.tidy_up()
            self.mon.log(self, "exiting because of error")
            #close logging files 
            self.mon.finish()
            exit()            
        else:
            self.tidy_up()
            self.mon.log(self,"no error - exiting normally")
            #close logging files 
            self.mon.finish()
            if self.shutdown_required==True:
                call(['sudo', 'shutdown', '-h', '-t 5','now'])
                exit()
            else:
                exit()


    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank']==True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up gpio
        if self.options['gpio']==True and self.ppio<>None:
            self.ppio.terminate()
            
        #tidy up time of day scheduler
        if self.tod<>None:
            self.tod.terminate()



# *****************************
# utilitities
# ****************************

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value
class VideoPlayer:
    """ plays a track using omxplayer
        See pp_imageplayer for common software design description
    """

    _CLOSED = "omx_closed"    #probably will not exist
    _STARTING = "omx_starting"  #track is being prepared
    _PLAYING = "omx_playing"  #track is playing to the screen, may be paused
    _ENDING = "omx_ending"  #track is in the process of ending due to quit or end of track


# ***************************************
# EXTERNAL COMMANDS
# ***************************************

    def __init__(self,
                         show_id,
                         root,
                        canvas,
                        show_params,
                        track_params ,
                         pp_dir,
                        pp_home,
                        pp_profile):

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_id=show_id
        self.root=root
        self.canvas = canvas
        self.show_params=show_params
        self.track_params=track_params
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile


        # get config from medialist if there.
        if self.track_params['omx-audio']<>"":
            self.omx_audio= self.track_params['omx-audio']
        else:
            self.omx_audio= self.show_params['omx-audio']
        if self.omx_audio<>"": self.omx_audio= "-o "+ self.omx_audio
        
        if self.track_params['omx-volume']<>"":
            self.omx_volume= self.track_params['omx-volume']
        else:
            self.omx_volume= self.show_params['omx-volume']
        if self.omx_volume<>"":
            self.omx_volume= "--vol "+ str(int(self.omx_volume)*100) + ' '

        if self.track_params['omx-window']<>'':
            self.omx_window= self.track_params['omx-window']
        else:
            self.omx_window= self.show_params['omx-window']


        # get background image from profile.
        self.background_file=''
        if self.track_params['background-image']<>"":
            self.background_file= self.track_params['background-image']
        else:
            if self.track_params['display-show-background']=='yes':
                self.background_file= self.show_params['background-image']
            
        # get background colour from profile.
        if self.track_params['background-colour']<>"":
            self.background_colour= self.track_params['background-colour']
        else:
            self.background_colour= self.show_params['background-colour']
        
        self.centre_x = int(self.canvas['width'])/2
        self.centre_y = int(self.canvas['height'])/2
        
        #get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']

        # open the plugin Manager
        self.pim=PluginManager(self.show_id,self.root,self.canvas,self.show_params,self.track_params,self.pp_dir,self.pp_home,self.pp_profile) 

        #create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()        
        
        # could put instance generation in play, not sure which is better.
        self.omx=OMXDriver(self.canvas)
        self.tick_timer=None
        self.init_play_state_machine()



    def play(self, track,
                     showlist,
                     end_callback,
                     ready_callback,
                     enable_menu=False):
                         
        #instantiate arguments
        self.track=track
        self.showlist=showlist
        self.ready_callback=ready_callback   #callback when ready to play
        self.end_callback=end_callback         # callback when finished
        self.enable_menu = enable_menu
 
        # callback to the calling object to e.g remove egg timer and enable click areas.
        if self.ready_callback<>None:
            self.ready_callback()

        # create an  instance of showmanager so we can control concurrent shows
        self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        #set up video window
        reason,message,comand,has_window,x1,y1,x2,y2= self.parse_window(self.omx_window)
        if reason =='error':
            self.mon.err(self,'omx window error: ' + message + ' in ' + self.omx_window)
            self.end_callback(reason,message)
        else:
            if has_window==True:
                self.omx_window= '--win " '+ str(x1) +  ' ' + str(y1) + ' ' + str(x2) + ' ' + str(y2) + ' " '
            else:
                self.omx_window=''

             # Control other shows at beginning
            reason,message=self.show_manager.show_control(self.track_params['show-control-begin'])
            if reason in ('error','killed'):
                self.end_callback(reason,message)
                self=None
            else:      
                #display content
                reason,message=self.display_content()
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                    # create animation events
                    reason,message=self.ppio.animate(self.animate_begin_text,id(self))
                    if reason=='error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        # start playing the video.
                        if self.play_state == VideoPlayer._CLOSED:
                            self.mon.log(self,">play track received")
                            self.start_play_state_machine(self.track)
                        else:
                            self.mon.err(self,'play track rejected')
                            self.end_callback('error','play track rejected')
                            self=None

    def terminate(self,reason):
        # circumvents state machine and does not wait for omxplayer to close
        if self.omx<>None:
            self.mon.log(self,"sent terminate to omxdriver")
            self.omx.terminate(reason)
            self.end('killed',' end without waiting for omxplayer to finish') # end without waiting
        else:
            self.mon.log(self,"terminate, omxdriver not running")
            self.end('killed','terminate, mplayerdriver not running')


    def input_pressed(self,symbol):
        if symbol[0:4]=='omx-':
            self.control(symbol[4])
            
        elif symbol =='pause':
            self.pause()

        elif symbol=='stop':
            self.stop()
        else:
            pass


    def get_links(self):
        return self.track_params['links']

            
                
# ***************************************
# INTERNAL FUNCTIONS
# ***************************************

    # respond to normal stop
    def stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,">stop received")
        self.quit_signal=True


    #toggle pause
    def pause(self):
        if self.play_state in (VideoPlayer._PLAYING,VideoPlayer._ENDING):
            self.omx.pause()
            return True
        else:
            self.mon.log(self,"!<pause rejected")
            return False
        
    # other control when playing
    def control(self,char):
        if self.play_state==VideoPlayer._PLAYING and char not in ('q'):
            self.mon.log(self,"> send control to omx: "+ char)
            self.omx.control(char)
            return True
        else:
            self.mon.log(self,"!<control rejected")
            return False



# ***********************
# sequencing
# **********************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the omx process is not running, omx process can be initiated
         - _starting - omx process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - omx is doing its termination, controls cannot be sent
    """

    def init_play_state_machine(self):
        self.quit_signal=False
        self.play_state=VideoPlayer._CLOSED
 
    def start_play_state_machine(self,track):
        #initialise all the state machine variables
        #self.iteration = 0                             # for debugging
        self.quit_signal=False     # signal that user has pressed stop
        self.play_state=VideoPlayer._STARTING
        
        #play the selected track
        options=self.omx_audio+ " " + self.omx_volume + ' ' + self.omx_window + ' ' + self.show_params['omx-other-options']+" "
        self.omx.play(track,options)
        self.mon.log (self,'Playing track from show Id: '+ str(self.show_id))
        # and start polling for state changes
        self.tick_timer=self.canvas.after(50, self.play_state_machine)
 

    def play_state_machine(self):      
        if self.play_state == VideoPlayer._CLOSED:
            self.mon.log(self,"      State machine: " + self.play_state)
            return 
                
        elif self.play_state == VideoPlayer._STARTING:
            self.mon.log(self,"      State machine: " + self.play_state)
            
            # if omxplayer is playing the track change to play state
            if self.omx.start_play_signal==True:
                self.mon.log(self,"            <start play signal received from omx")
                self.omx.start_play_signal=False
                self.play_state=VideoPlayer._PLAYING
                self.mon.log(self,"      State machine: omx_playing started")
            self.tick_timer=self.canvas.after(50, self.play_state_machine)

        elif self.play_state == VideoPlayer._PLAYING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # service any queued stop signals
            if self.quit_signal==True:
                self.mon.log(self,"      Service stop required signal")
                self.stop_omx()
                self.quit_signal=False
                # self.play_state = VideoPlayer._ENDING
                
            # omxplayer reports it is terminating so change to ending state
            if self.omx.end_play_signal:                    
                self.mon.log(self,"            <end play signal received")
                self.mon.log(self,"            <end detected at: " + str(self.omx.video_position))
                if self.omx.end_play_reason<>'nice_day':
                    # deal with omxplayer not sending 'have a nice day'
                    self.mon.warn(self,"            <end detected at: " + str(self.omx.video_position))
                    self.mon.warn(self,"            <pexpect reports: "+self.omx.end_play_reason)
                    self.mon.warn(self,'pexpect.before  is'+self.omx.xbefore)
                self.play_state = VideoPlayer._ENDING
                self.ending_count=0
                
            self.tick_timer=self.canvas.after(200, self.play_state_machine)

        elif self.play_state == VideoPlayer._ENDING:
            self.mon.log(self,"      State machine: " + self.play_state)
            # if spawned process has closed can change to closed state
            self.mon.log (self,"      State machine : is omx process running? -  "  + str(self.omx.is_running()))
            if self.omx.is_running() ==False:
                self.mon.log(self,"            <omx process is dead")
                self.play_state = VideoPlayer._CLOSED
                self.end('normal','quit by user or system')
            else:
                self.ending_count+=1
                if self.ending_count>10:
                    # deal with omxplayer not terminating at the end of a track
                    self.mon.warn(self,"            <omxplayer failed to close at: " + str(self.omx.video_position))
                    self.mon.warn(self,'pexpect.before  is'+self.omx.xbefore)
                    self.omx.kill()
                    self.mon.warn(self,'omxplayer now  terminated ')
                    self.play_state = VideoPlayer._CLOSED
                    self.end('normal','end from omxplayer failed to terminate')
                else:
                    self.tick_timer=self.canvas.after(200, self.play_state_machine)

    def stop_omx(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,"         >stop omx received from state machine")
        if self.play_state==VideoPlayer._PLAYING:
            self.omx.stop()
            return True
        else:
            self.mon.log(self,"!<stop rejected")
            return False




# *****************
# ending the player
# *****************

    def end(self,reason,message):

            # stop the plugin
            if self.track_params['plugin']<>'':
                self.pim.stop_plugin()

            # os.system("xrefresh -display :0")
            # abort the timer
            if self.tick_timer<>None:
                self.canvas.after_cancel(self.tick_timer)
                self.tick_timer=None
            
            if reason in ('error','killed'):
                self.end_callback(reason,message)
                self=None

            else:
                # normal end so do show control and animation

                # Control concurrent shows at end
                reason,message=self.show_manager.show_control(self.track_params['show-control-end'])
                if reason =='error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                   # clear events list for this track
                    if self.track_params['animate-clear']=='yes':
                        self.ppio.clear_events_list(id(self))
                    
                    # create animation events for ending
                    reason,message=self.ppio.animate(self.animate_begin_text,id(self))
                    if reason=='error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        self.end_callback('normal',"track has terminated or quit")
                        self=None



# *****************
# displaying things
# *****************
    def display_content(self):

        #background colour
        if  self.background_colour<>'':   
           self.canvas.config(bg=self.background_colour)
            
        # delete previous content
        self.canvas.delete('pp-content')

        # background image
        if self.background_file<>'':
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(self,"Video background file not found: "+ self.background_img_file)
                self.end('error',"Video background file not found")
            else:
                pil_background_img=PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                             int(self.canvas['height'])/2,
                                             image=self.background,
                                            anchor=CENTER,
                                            tag='pp-content')

        # execute the plugin if required
        if self.track_params['plugin']<>'':

            reason,message,self.track = self.pim.do_plugin(self.track,self.track_params['plugin'],)
            if reason <> 'normal':
                return reason,message

                          
        # display show text if enabled
        if self.show_params['show-text']<> '' and self.track_params['display-show-text']=='yes':
            self.canvas.create_text(int(self.show_params['show-text-x']),int(self.show_params['show-text-y']),
                                                    anchor=NW,
                                                  text=self.show_params['show-text'],
                                                  fill=self.show_params['show-text-colour'],
                                                  font=self.show_params['show-text-font'],
                                                  tag='pp-content')


        # display track text if enabled
        if self.track_params['track-text']<> '':
            self.canvas.create_text(int(self.track_params['track-text-x']),int(self.track_params['track-text-y']),
                                                    anchor=NW,
                                                  text=self.track_params['track-text'],
                                                  fill=self.track_params['track-text-colour'],
                                                  font=self.track_params['track-text-font'],
                                                  tag='pp-content')

        # display instructions if enabled
        if self.enable_menu== True:
            self.canvas.create_text(int(self.show_params['hint-x']),
                                                    int(self.show_params['hint-y']),
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                font=self.show_params['hint-font'],
                                                anchor=NW,
                                                tag='pp-content')

        self.canvas.tag_raise('pp-click-area')
        self.canvas.update_idletasks( )
        return 'normal',''


# ****************
# utilities
# *****************

    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Background image is "+ track_file)
        return track_file

# original _
# warp _ or xy2


    def parse_window(self,line):
        
            fields = line.split()
            # check there is a command field
            if len(fields) < 1:
                    return 'error','no type field','',False,0,0,0,0
                
            # deal with original which has 1
            if fields[0]=='original':
                if len(fields) <> 1:
                        return 'error','number of fields for original','',False,0,0,0,0    
                return 'normal','',fields[0],False,0,0,0,0


            #deal with warp which has 1 or 5  arguments
            # check basic syntax
            if  fields[0] <>'warp':
                    return 'error','not a valid type','',False,0,0,0,0
            if len(fields) not in (1,5):
                    return 'error','wrong number of coordinates for warp','',False,0,0,0,0

            # deal with window coordinates    
            if len(fields) == 5:
                #window is specified
                if not (fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit() and fields[4].isdigit()):
                    return 'error','coordinates are not positive integers','',False,0,0,0,0
                has_window=True
                return 'normal','',fields[0],has_window,int(fields[1]),int(fields[2]),int(fields[3]),int(fields[4])
            else:
                # fullscreen
                has_window=True
                return 'normal','',fields[0],has_window,0,0,self.canvas['width'],self.canvas['height']
class RadioButtonShow:
    """
        starts at 'first-track' which can be any type of track or a show
        The show has links of the form symbolic-name play track-ref
        key, gpio or click area will play the referenced track
        at the end of that track control will return to first-track
        links in the tracks are ignored. Links are inherited from the show.
        timeout returns to first-track

        interface:
         * play - selects the first track to play (first-track) 
         * input_pressed,  - receives user events passes them to a Shower/Player if a track is playing,
                otherwise actions them depending on the symbolic name supplied
    """

# *********************
# external interface
# ********************

    def __init__(self,
                            show_params,
                             root,
                            canvas,
                            showlist,
                             pp_dir,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the tracks of the event show are to be written on
            show_params - the name of the configuration dictionary section for the radiobuttonshow
            showlist  - the showlist, to enable runningnof show type tracks.
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """
        
        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_params=show_params
        self.showlist=showlist
        self.root=root
        self.canvas=canvas
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()

      
        #create a path stack - only used to parse the links.
        self.path = PathManager()
        
        # init variables
        self.drawn  = None
        self.player=None
        self.shower=None
        self.timeout_running=None
        self.error=False



    def play(self,show_id,end_callback,ready_callback,top=False,command='nil'):
        """ starts the hyperlink show at start-track 
              end_callback - function to be called when the show exits
              ready_callback - callback when event-show is ready to display its forst track (not used?)
              top is True when the show is top level (run from [start] or from show control)
              command is not used
        """
        
        #instantiate arguments
        self.show_id=show_id
        self.end_callback=end_callback
        self.ready_callback=ready_callback
        self.top=top
        self.command=command

        # check data files are available.
        self.medialist_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.medialist_file):
            self.mon.err(self,"Medialist file not found: "+ self.medialist_file)
            self.end('error',"Medialist file not found")
        
        #create a medialist object for the radiobuttonshow and read the file into it.
        self.medialist=MediaList()
        if self.medialist.open_list(self.medialist_file,self.showlist.sissue()) == False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self.end('error',"Version of medialist different to Pi Presents")
        
        # read show destinations
        self.first_track_ref=self.show_params['first-track-ref']

        #get control bindings for this show if top level
        controlsmanager=ControlsManager()
        if self.top==True:
            self.controls_list=controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list=controlsmanager.merge_show_controls(self.controls_list,self.show_params['controls'])


        #read the show links. Track links will be added by ready_callback
        links_text=self.show_params['links']
        reason,message,self.links=self.path.parse_links(links_text)
        if reason=='error':
            self.mon.err(self,message + " in show")
            self.end('error',message)
        
        # state variables and signals   
        self.end_radiobuttonshow_signal= False
        self.egg_timer=None
        self.next_track_signal=False
        self.next_track_ref=''
        self.current_track_ref=''
        self.current_track_type=''

        # ready callback for show
        if self.ready_callback<>None:
            self.ready_callback()
                    
        self.canvas.delete('pp-content')
        self.canvas.config(bg='black')
        
        self.do_first_track()

        
#stop received from another concurrent show via ShowManager

    def managed_stop(self):
            # set signal to stop the radiobuttonshow when all  sub-shows and players have ended
            self.end_radiobuttonshow_signal=True
            # then stop and shows or tracks.
            if self.shower<>None:
                self.shower.managed_stop()
            elif self.player<>None:
                self.player.input_pressed('stop')
            else:
                self.end('normal','stopped by ShowManager')
                

    # kill or error
    def terminate(self,reason):
        self.end_radiobuttonshow_signal=True
        if self.shower<>None:
            self.shower.terminate(reason)
        elif self.player<>None:
            self.player.terminate(reason)
        else:
            self.end(reason,'terminated without terminating shower or player')


   # respond to inputs
    def input_pressed(self,symbol,edge,source):

        self.mon.log(self,"received symbol: " + symbol)

        #does the symbol match a link, if so execute it
        if self.is_link(symbol,edge,source)==True:
            return

        # controls are disabled so ignore inputs
        if self.show_params['disable-controls']=='yes':
            return

        # does it match a control       
        # if at top convert symbolic name to operation otherwise lower down we have received an operatio    
        # look through list of controls to find match
        if self.top==True:
            operation=self.lookup_control(symbol,self.controls_list)
        else:
            operation=symbol
        # print 'operation',operation 
        if operation<>'':
            self.do_operation(operation,edge,source)


    def do_operation(self,operation,edge,source):
        if self.shower<>None:
            # if next lower show is running pass down to stop the show and lower level
            self.shower.input_pressed(operation,edge,source)
        else:
            #control this show and its tracks
            if operation=='stop':
                if self.player<>None:
                    if self.current_track_ref==self.first_track_ref and self.top==False:
                        self.end_radiobuttonshow_signal=True
                    self.player.input_pressed('stop')
                    
            elif operation == 'pause':
                if self.player<>None:
                    self.player.input_pressed(operation)
                    
            elif operation[0:4]=='omx-' or operation[0:6]=='mplay-'or operation[0:5]=='uzbl-':
                if self.player<>None:
                    self.player.input_pressed(operation)


    def lookup_control(self,symbol,controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''


    def is_link(self,symbol,edge,source):
        # we have links which locally define symbolic names to be converted to radiobuttonshow operations
        # find the first entry in links that matches the symbol and execute its operation
        #print 'radiobuttonshow ',symbol
        found=False
        for link in self.links:
            #print link
            if symbol==link[0]:
                found=True
                if link[1]<>'null':
                    #print 'match',link[0]
                    link_operation=link[1]
                    if link_operation=='play':
                        self.do_play(link[2],edge,source)
        return found



# *********************
# INTERNAL FUNCTIONS
# ********************

# *********************
# Show Sequencer
# *********************


    def timeout_callback(self):
        self.do_play(self.first_track_ref,'front','timeout')

    def do_play(self,track_ref,edge,source):
        if track_ref<>self.current_track_ref:
            # print 'executing play ',track_ref
            self.next_track_signal=True
            self.next_track_op='play'
            self.next_track_arg=track_ref
            if self.shower<>None:
                self.shower.input_pressed('stop',edge,source)
            elif self.player<>None:
                self.player.input_pressed('stop')
            else:
                self.what_next()



    def do_first_track(self):
        index = self.medialist.index_of_track(self.first_track_ref)
        if index >=0:
            #don't use select the track as not using selected_track in radiobuttonshow
            first_track=self.medialist.track(index)
            self.path.append(first_track['track-ref'])
            self.current_track_ref=self.first_track_ref
            self.play_selected_track(first_track)
        else:
            self.mon.err(self,"first-track not found in medialist: "+ self.show_params['first-frack-ref'])
            self.end('error',"first track not found in medialist")

            

    def what_next(self):
        # user wants to end the show 
        if self.end_radiobuttonshow_signal==True:
            self.end_radiobuttonshow_signal=False
            self.end('normal',"show ended by user")

        # user has selected another track
        elif self.next_track_signal==True:
                self.next_track_signal=False
                self.next_track_ref=self.next_track_arg        
                self.current_track_ref=self.next_track_ref                    
                index = self.medialist.index_of_track(self.next_track_ref)
                if index >=0:
                    #don't use select the track as not using selected_track in radiobuttonshow
                    next_track=self.medialist.track(index)
                    self.play_selected_track(next_track)
                else:
                    self.mon.err(self,"next-track not found in medialist: "+ self.next_track_ref)
                    self.end('error',"next track not found in medialist")
                    
        else:
            #track ends naturally
            self.next_track_ref=self.first_track_ref
            self.current_track_ref=self.next_track_ref                    
            index = self.medialist.index_of_track(self.next_track_ref)
            if index >=0:
                #don't use select the track as not using selected_track in radiobuttonshow
                next_track=self.medialist.track(index)
                self.play_selected_track(next_track)
            else:
                self.mon.err(self,"next-track not found in medialist: "+ self.next_track_ref)
                self.end('error',"next track not found in medialist")



# *********************
# Dispatching to Players
# *********************


    def page_callback(self):
        # called from a Player when ready to play, if first-track merge the links from the track with those from the show
        self.delete_eggtimer()
        if self.current_track_ref==self.first_track_ref:
            #links_text=self.player.get_links()
            #reason,message,track_links=self.path.parse_links(links_text)
            #if reason=='error':
                #self.mon.err(self,message + " in page")
                #self.end('error',message)
            #self.path.merge_links(self.links,track_links)
            pass

           
    def play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """     

        if self.timeout_running<>None:
            self.canvas.after_cancel(self.timeout_running)
            self.timeout_running=None
            
        # self.display_eggtimer(self.resource('radiobuttonshow','m01'))

        self.current_track_type = selected_track['type']
        

        #start timeout for the track if required           
             
        if self.current_track_ref<>self.first_track_ref and int(self.show_params['timeout'])<>0:
            self.timeout_running=self.canvas.after(int(self.show_params['timeout'])*1000,self.timeout_callback)
        

        # dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
        
        if track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.page_callback,
                                        enable_menu=False)
                                        
        elif track_type=="audio":
            # create a audioplayer
            track_file=self.complete_path(selected_track)
            self.player=AudioPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.page_callback,
                                        enable_menu=False)
                                        
        elif track_type=="image":
            track_file=self.complete_path(selected_track)
            self.player=ImagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                    self.showlist,
                                    self.end_player,
                                    self.page_callback,
                                    enable_menu=False,
                                    )

        elif track_type=="web":
            # create a browser
            track_file=self.complete_path(selected_track)
            self.player=BrowserPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.page_callback,
                                        enable_menu=False)
                                    
                         
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(text,
                                    self.showlist,
                                    self.end_player,
                                    self.page_callback,
                                    enable_menu=False
                                    )

 
        elif track_type=="show":
            # self.enable_click_areas()
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self.end("Unknown show")
            
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                               self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                               self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="liveshow":    
                self.shower= LiveShow(selected_show,
                                                                  self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                                  self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="radiobuttonshow":
                self.shower= RadioButtonShow(selected_show,
                                                         self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                         self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="hyperlinkshow":
                self.shower= HyperlinkShow(selected_show,
                                                       self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                       self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="menu": 
                self.shower= MenuShow(selected_show,
                                                          self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')                    
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self.end("Unknown show type")  
                
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self.end("Unknown track type")

    
    # callback from when player ends
    def end_player(self,reason,message):
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        # this does not seem to change the colour of the polygon
        # self.canvas.itemconfig('pp-click-area',state='hidden')
        self.canvas.update_idletasks( )
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            #self.display_eggtimer(self.resource('radiobuttonshow','m02'))
            self.what_next()

    # callback from when shower ends
    def end_shower(self,show_id,reason,message):
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        # self.canvas.itemconfig('pp-click-area',state='hidden')
        self.canvas.update_idletasks( )
        if reason in ("killed","error"):
            self.end(reason,message)
        else:
            #self.display_eggtimer(self.resource('radiobuttonshow','m03'))
            self.what_next()  


# *********************
# End the show
# *********************
    # finish the player for killing, error or normally
    # this may be called directly sub/child shows or players are not running
    # if they might be running then need to call terminate.

    def end(self,reason,message):
        self.mon.log(self,"Ending radiobuttonshow: "+ self.show_params['show-ref'])  
        self.end_callback(self.show_id,reason,message)
        self=None
        return




# *********************
# displaying things
# *********************

    def display_eggtimer(self,text):
        #self.egg_timer=self.canvas.create_text(int(self.canvas['width'])/2,
                                              #int(self.canvas['height'])/2,
                                                  #text= text,
                                                # fill='white',
                                               # font="Helvetica 20 bold")
        #self.canvas.update_idletasks( )
        pass


    def delete_eggtimer(self):
        if self.egg_timer!=None:
            self.canvas.delete(self.egg_timer)

# *********************
# utilities
# *********************

    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file<>'' and track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to play is: "+ track_file)
        return track_file     


    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            # players or showers may be running so need terminate
            self.terminate("error")
        else:
            return value
Example #15
0
class TimeOfDay(object):

    # CLASS VARIABLES

    # change this for another language
    DAYS_OF_WEEK = [
        'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday',
        'sunday'
    ]
    """
        TimeOfDay.events is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements sorted by descending time
        Each time element is a list with the fields:
        0 - command
        1 - time, seconds since midnight

    """
    events = {
    }  # list of times of day used to generate callbacks, earliest first

    # executed by main program and by each object using tod
    def __init__(self):
        self.mon = Monitor()

    # executed once from main program  only
    def init(self, pp_dir, pp_home, pp_profile, showlist, root, callback):

        # instantiate arguments
        TimeOfDay.root = root
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.showlist = showlist
        self.callback = callback

        # init variables
        self.testing = False
        self.tod_tick = 500
        self.tick_timer = None
        # set time of day for if no schedule
        TimeOfDay.now = datetime.now().replace(microsecond=0)

        #  read and error check the schedule
        reason, message, schedule_enabled = self.read_schedule()
        if reason == 'error':
            return 'error', message, False

        if schedule_enabled is False:
            return 'normal', '', False

        #create the initial events list
        if self.simulate_time is True:
            year = int(self.sim_year)
            month = int(self.sim_month)
            day = int(self.sim_day)
            hour = int(self.sim_hour)
            minute = int(self.sim_minute)
            second = int(self.sim_second)
            TimeOfDay.now = datetime(day=day,
                                     month=month,
                                     year=year,
                                     hour=hour,
                                     minute=minute,
                                     second=second)
            self.testing = True
            print '\nInitial SIMULATED time', TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is ON, Initial SIMULATED time ' +
                str(TimeOfDay.now.ctime()))
        else:
            #get the current date/time only this once
            TimeOfDay.now = datetime.now().replace(microsecond=0)
            self.mon.sched(
                self, TimeOfDay.now, 'Testing is OFF, Initial REAL time ' +
                str(TimeOfDay.now.ctime()))
            # print '\nInitial REAL time',TimeOfDay.now.ctime()
            self.testing = False
        # print 'init',TimeOfDay.now
        TimeOfDay.last_now = TimeOfDay.now - timedelta(seconds=1)
        reason, message = self.build_schedule_for_today()
        if reason == 'error':
            return 'error', message, False
        self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
        if self.testing:
            self.print_todays_schedule()
        self.build_events_lists()
        if self.testing:
            self.print_events_lists()
        # and do exitpipresents or start any show that should be running at start up time
        self.do_catchup()
        return 'normal', '', True

    def do_catchup(self):
        TimeOfDay.scheduler_time = TimeOfDay.now.time()

        # do the catchup for each real show in turn
        # nothing required for start show, all previous events are just ignored.
        for show_ref in TimeOfDay.events:
            if show_ref != 'start' and self.enable_catchup[show_ref] == 'yes':
                if self.testing:
                    print 'Catch Up', show_ref
                times = TimeOfDay.events[show_ref]
                # go through the event list for a show rembering show state until the first future event is found.
                # then if last command was to start the show send it
                show_running = False
                last_start_element = []
                for time_element in reversed(times):
                    # print 'now', now_seconds, 'time from events', time_element[1], time_element[0]
                    # got past current time can give up catch up
                    if time_element[1] >= TimeOfDay.scheduler_time:
                        # print ' gone past time - break'
                        break
                    if time_element[0] == 'open':
                        last_start_element = time_element
                        # print 'open - show-running= true'
                        show_running = True
                    elif time_element[0] == 'close':
                        # print 'close - show-running= false'
                        show_running = False
                if show_running is True:
                    #if self.testing:
                    # print 'End of Catch Up Search', show_ref,last_start_element
                    self.mon.sched(
                        self, TimeOfDay.now, 'Catch up for show: ' + show_ref +
                        ' requires ' + last_start_element[0] + ' ' +
                        str(last_start_element[1]))
                    self.do_event(show_ref, last_start_element)

        return 'not exiting'

    # called by main program only
    def poll(self):
        if self.testing:
            poll_time = TimeOfDay.now
        else:
            poll_time = datetime.now()
        # print 'poll time: ',poll_time.time(),'scheduler time: ',TimeOfDay.now.time()
        # if poll_time != TimeOfDay.now : print 'times different ',poll_time.time(),TimeOfDay.now.time()
        # is current time greater than last time the scheduler was run
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        # poll time can be the same twice as poll is run at half second intervals.
        catchup_time = 0
        while TimeOfDay.now <= poll_time:
            if TimeOfDay.now - TimeOfDay.last_now != timedelta(seconds=1):
                print 'POLL TIME FAILED', TimeOfDay.last_now, TimeOfDay.now
            #if catchup_time != 0:
            # print 'scheduler behind by: ',catchup_time, TimeOfDay.now.time(),poll_time.time()
            self.do_scheduler()
            TimeOfDay.last_now = TimeOfDay.now
            catchup_time += 1
            # print 'poll',TimeOfDay.now, timedelta(seconds=1)
            TimeOfDay.now = TimeOfDay.now + timedelta(seconds=1)
        # and loop
        if self.testing:
            self.tick_timer = TimeOfDay.root.after(1000, self.poll)
        else:
            self.tick_timer = TimeOfDay.root.after(self.tod_tick, self.poll)

    # called by main program only
    def terminate(self):
        if self.tick_timer is not None:
            TimeOfDay.root.after_cancel(self.tick_timer)
        self.clear_events_lists()

    # execute events at the appropriate time.
    # called by main program only
    def do_scheduler(self):
        # if its midnight then build the events lists for the new day
        TimeOfDay.scheduler_time = TimeOfDay.now.time()
        if TimeOfDay.scheduler_time == time(hour=0, minute=0, second=0):
            # if self.testing:
            # print 'Its midnight,  today is now', TimeOfDay.now.ctime()
            self.mon.sched(
                self, TimeOfDay.now,
                'Its midnight,  today is now ' + str(TimeOfDay.now.ctime()))
            reason, message = self.build_schedule_for_today()
            if reason == 'error':
                self.mon.err(self, 'system error- illegal time at midnight')
                return
            self.mon.sched(self, TimeOfDay.now, self.pretty_todays_schedule())
            # if self.testing:
            # self.print_todays_schedule()
            self.build_events_lists()
            # self.mon.sched(self,TimeOfDay.now,self.pretty_events_lists())
            # self.print_events_lists()

        # print TimeOfDay.scheduler_time
        for show_ref in TimeOfDay.events:
            # print 'scheduler time match', show_ref
            times = TimeOfDay.events[show_ref]
            # now send a command if time matches
            for time_element in reversed(times):
                # print time_element[1],TimeOfDay.scheduler_time
                if time_element[1] == TimeOfDay.scheduler_time:
                    self.do_event(show_ref, time_element)

    # execute an event
    def do_event(self, show_ref, time_element):
        self.mon.log(
            self, 'Event : ' + time_element[0] + ' ' + show_ref +
            ' required at: ' + time_element[1].isoformat())
        self.mon.sched(
            self, TimeOfDay.now, ' ToD Scheduler : ' + time_element[0] + ' ' +
            show_ref + ' required at: ' + time_element[1].isoformat())
        if self.testing:
            print 'Event : ' + time_element[
                0] + ' ' + show_ref + ' required at: ' + time_element[
                    1].isoformat()
        if show_ref != 'start':
            self.callback(time_element[0] + ' ' + show_ref)
        else:
            self.callback(time_element[0])

#
# ************************************************
# The methods below can be called from many classes so need to operate on class variables
# ************************************************

# clear events list

    def clear_events_lists(self):
        self.mon.log(self, 'clear time of day  events list ')
        # empty event list
        TimeOfDay.events = {}

# ***********************************
# Preparing schedule and todays event list
# ************************************

    def read_schedule(self):
        # get schedule from showlist
        index = self.showlist.index_of_start_show()
        self.showlist.select(index)
        starter_show = self.showlist.selected_show()

        sched_enabled = starter_show['sched-enable']
        if sched_enabled != 'yes':
            return 'normal', '', False

        if starter_show['simulate-time'] == 'yes':
            self.simulate_time = True

            self.sim_second = starter_show['sim-second']
            if not self.sim_second.isdigit():
                return 'error', 'Simulate time -  second is not a positive integer ' + self.sim_second, False
            if int(self.sim_second) > 59:
                return 'error', 'Simulate time - second is out of range ' + self.sim_second, False

            self.sim_minute = starter_show['sim-minute']
            if not self.sim_minute.isdigit():
                return 'error', 'Simulate time - minute is not a positive integer ' + self.sim_minute, False
            if int(self.sim_minute) > 59:
                return 'error', 'Simulate time -  minute is out of range ' + self.sim_minute, False

            self.sim_hour = starter_show['sim-hour']
            if not self.sim_hour.isdigit():
                return 'error', 'Simulate time - hour is not a positive integer ' + self.sim_hour, False
            if int(self.sim_hour) > 23:
                return 'error', 'Simulate time -  hour is out of range ' + self.sim_hour, False

            self.sim_day = starter_show['sim-day']
            if not self.sim_day.isdigit():
                return 'error', 'Simulate time - day is not a positive integer ' + self.sim_day, False
            if int(self.sim_day) > 31:
                return 'error', 'Simulate time -  day is out of range ' + self.sim_day, False

            self.sim_month = starter_show['sim-month']
            if not self.sim_month.isdigit():
                return 'error', 'Simulate time - month is not a positive integer ' + self.sim_month, False
            if int(self.sim_month) > 12:
                return 'error', 'Simulate time -  month is out of range ' + self.sim_month, False

            self.sim_year = starter_show['sim-year']
            if not self.sim_year.isdigit():
                return 'error', 'Simulate time - year is not a positive integer ' + self.sim_year, False
            if int(self.sim_year) < 2019:
                return 'error', 'Simulate time -  year is out of range ' + self.sim_year, False
        else:
            self.simulate_time = False

        return 'normal', '', True

    def build_schedule_for_today(self):
        # print this_day.year, this_day.month, this_day.day, TimeOfDay.DAYS_OF_WEEK[ this_day.weekday()]
        """
        self.todays_schedule is a dictionary the keys being show-refs.
        Each dictionary entry is a list of time_elements
        Each time element is a list with the fields:
        0 - command
        1 - time hour:min[:sec]

        """
        self.enable_catchup = {}
        self.todays_schedule = {}
        for index in range(self.showlist.length()):
            show = self.showlist.show(index)
            show_type = show['type']
            show_ref = show['show-ref']
            if show['type'] != 'start':
                self.enable_catchup[show_ref] = show['enable-catchup']
            # print 'looping build ',show_type,show_ref,self.showlist.length()
            if 'sched-everyday' in show:
                text = show['sched-everyday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'everyday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'everyday ',status,message,days_list,times_list
                    self.todays_schedule[show['show-ref']] = copy.deepcopy(
                        times_list)

                # print '\nafter everyday'
                # self.print_todays_schedule()

            if 'sched-weekday' in show:
                text = show['sched-weekday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'weekday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'weekday ',status,message,days_list,times_list
                    # is current day of the week in list of days in schedule
                    if TimeOfDay.DAYS_OF_WEEK[
                            TimeOfDay.now.weekday()] in days_list:
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            times_list)

                #print '\nafter weekday'
                #self.print_todays_schedule()

            if 'sched-monthday' in show:
                text = show['sched-monthday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    # print 'in monthday',day_lines
                    if status == 'error':
                        return 'error', message
                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'monthday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    #print 'monthday ',status,message,days_list,times_list
                    if TimeOfDay.now.day in map(int, days_list):
                        self.todays_schedule[show['show-ref']] = copy.deepcopy(
                            times_list)

                #print '\nafter monthday'
                #self.print_todays_schedule()

            if 'sched-specialday' in show:

                text = show['sched-specialday']
                lines = text.splitlines()
                while len(lines) != 0:
                    status, message, day_lines, lines = self.get_one_day(
                        lines, show_ref)
                    if status == 'error':
                        return 'error', message

                    status, message, days_list, times_list = self.parse_day(
                        day_lines, 'specialday', show_ref, show_type)
                    if status == 'error':
                        return 'error', message
                    # print 'specialday ',status,message,days_list,times_list
                    for day in days_list:
                        sdate = datetime.strptime(day, '%Y-%m-%d')
                        if sdate.year == TimeOfDay.now.year and sdate.month == TimeOfDay.now.month and sdate.day == TimeOfDay.now.day:
                            self.todays_schedule[
                                show['show-ref']] = copy.deepcopy(times_list)

                #print '\nafter specialday'
                #self.print_todays_schedule()
        # print self.enable_catchup
        return 'normal', ''

    def get_one_day(self, lines, show_ref):
        this_day = []
        left_over = []
        #print 'get one day',lines
        # check first line is day and move tt output
        #print lines[0]
        if not lines[0].startswith('day'):
            return 'error', 'first line of section is not day ' + lines[
                0] + ' ' + show_ref, [], []
        this_day = [lines[0]]
        #print ' this day',this_day
        left_over = lines[1:]
        # print 'left over',left_over
        x_left_over = lines[1:]
        for line in x_left_over:
            #print 'in loop',line
            if line.startswith('day'):
                # print 'one day day',this_day,left_over
                return 'normal', '', this_day, left_over
            this_day.append(line)
            left_over = left_over[1:]
        # print 'one day end',this_day,left_over
        return 'normal', '', this_day, left_over

    def parse_day(self, lines, section, show_ref, show_type):
        # text
        # day monday
        # open 1:42
        # close 1:45
        # returns status,message,list of days,list of time lines
        if section == 'everyday':
            status, message, days_list = self.parse_everyday(
                lines[0], show_ref)
        elif section == 'weekday':
            status, message, days_list = self.parse_weekday(lines[0], show_ref)
        elif section == 'monthday':
            # print 'parse_day',lines
            status, message, days_list = self.parse_monthday(
                lines[0], show_ref)
        elif section == 'specialday':
            status, message, days_list = self.parse_specialday(
                lines[0], show_ref)
        else:
            return 'error','illegal section name '+section + ' '+ show_ref,[],[]
        if status == 'error':
            return 'error', message, [], []
        if len(lines) > 1:
            time_lines = lines[1:]
            status, message, times_list = self.parse_time_lines(
                time_lines, show_ref, show_type)
            if status == 'error':
                return 'error', message, [], []
        else:
            times_list = []
        return 'normal', '', days_list, times_list

    def parse_everyday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error','day line does not contain day  '+ line + ' ' + show_ref,[]
        if words[1] != 'everyday':
            return 'error','everday line does not contain everyday  '+ show_ref,[]
        return 'normal', '', ['everyday']

    def parse_weekday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error','day line does not contain day  ' + line + ' ' + show_ref,[]
        days = words[1:]
        for day in days:
            if day not in TimeOfDay.DAYS_OF_WEEK:
                return 'error','weekday line has illegal day '+ day + ' '+ show_ref,[]
        return 'normal', '', days

    def parse_monthday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error', 'day line does not contain day  ' + show_ref, []
        days = words[1:]
        for day in days:
            if not day.isdigit():
                return 'error','monthday line has illegal day '+ day + ' '+ show_ref,[]
            if int(day) < 1 or int(day) > 31:
                return 'error','monthday line has out of range day '+ line+ ' '+ show_ref,[]
        return 'normal', '', days

    def parse_specialday(self, line, show_ref):
        words = line.split()
        if words[0] != 'day':
            return 'error', 'day line does not contain day  ' + show_ref, []
        days = words[1:]
        for day in days:
            status, message = self.parse_date(day, show_ref)
            if status == 'error':
                return 'error', message, ''
        return 'normal', '', days

    def parse_time_lines(self, lines, show_ref, show_type):
        # lines - list of  lines each with text 'command time'
        # returns list of lists each being [command, time]
        time_lines = []
        for line in lines:
            # split line into time,command
            words = line.split()
            if len(words) < 2:
                return 'error','time line has wrong length '+ line+ ' '+ show_ref,[]
            status, message, time_item = self.parse_time(words[0], show_ref)
            if status == 'error':
                return 'error', message, []

            if show_type == 'start':
                command = ' '.join(words[1:])
                time_lines.append([command, time_item])
            else:
                if words[1] not in ('open', 'close'):
                    return 'error','illegal command in '+ line+ ' '+ show_ref,[]
                time_lines.append([words[1], time_item])
        return 'normal', '', time_lines

    def build_events_lists(self):
        # builds events dictionary from todays_schedule by
        # converting times in todays schedule from hour:min:sec to datetime
        # and sorts them earliest last
        TimeOfDay.events = {}
        for show_ref in self.todays_schedule:
            # print show_ref
            times = self.todays_schedule[show_ref]
            for time_element in times:
                time_element[1] = self.parse_event_time(time_element[1])
            sorted_times = sorted(times,
                                  key=lambda time_element: time_element[1],
                                  reverse=True)
            TimeOfDay.events[show_ref] = sorted_times
            # print times

    def parse_event_time(self, time_text):
        fields = time_text.split(':')
        if len(fields) > 2:
            secs = int(fields[2])
        else:
            secs = 0
        hours = int(fields[0])
        mins = int(fields[1])
        return time(hour=hours, minute=mins, second=secs)

    def parse_time(self, item, show_ref):
        fields = item.split(':')
        if len(fields) == 0:
            return 'error', 'Time field is empty ' + item + ' ' + show_ref, item
        if len(fields) > 3:
            return 'error', 'Too many fields in ' + item + ' ' + show_ref, item
        if len(fields) == 1:
            seconds = fields[0]
            minutes = '0'
            hours = '0'
        if len(fields) == 2:
            seconds = fields[1]
            minutes = fields[0]
            hours = '0'
        if len(fields) == 3:
            seconds = fields[2]
            minutes = fields[1]
            hours = fields[0]
        if not seconds.isdigit() or not minutes.isdigit() or not hours.isdigit(
        ):
            return 'error', 'Fields of  ' + item + ' are not positive integers ' + show_ref, item
        if int(minutes) > 59:
            return 'error', 'Minutes of  ' + item + ' is out of range ' + show_ref, item
        if int(seconds) > 59:
            return 'error', 'Seconds of  ' + item + ' is out of range ' + show_ref, item
        if int(hours) > 23:
            return 'error', 'Hours of  ' + item + ' is out of range ' + show_ref, item
        return 'normal', '', item

    def parse_date(self, item, show_ref):
        fields = item.split('-')
        if len(fields) == 0:
            return 'error', 'Date field is empty ' + item + ' ' + show_ref
        if len(fields) != 3:
            return 'error', 'Too many or few fields in date ' + item + ' ' + show_ref
        year = fields[0]
        month = fields[1]
        day = fields[2]
        if not year.isdigit() or not month.isdigit() or not day.isdigit():
            return 'error', 'Fields of  ' + item + ' are not positive integers ' + show_ref
        if int(year) < 2018:
            return 'error', 'Year of  ' + item + ' is out of range ' + show_ref + year
        if int(month) > 12:
            return 'error', 'Month of  ' + item + ' is out of range ' + show_ref
        if int(day) > 31:
            return 'error', 'Day of  ' + item + ' is out of range ' + show_ref
        return 'normal', ''

# *********************
# print for debug
# *********************

    def pretty_todays_schedule(self):
        op = 'Schedule For ' + TimeOfDay.now.ctime() + '\n'
        for key in self.todays_schedule:
            op += '  ' + key + '\n'
            for show in self.todays_schedule[key]:
                op += '    ' + show[0] + ':   ' + str(show[1]) + '\n'
            op += '\n'
        return op

    def pretty_events_lists(self):
        op = ' Task list for today'
        for key in self.events:
            op += '\n' + key
            for show in self.events[key]:
                op += '\n    ' + show[0] + ' ' + str(show[1].isoformat())
        return op

    def print_todays_schedule(self):
        print '\nSchedule For ' + TimeOfDay.now.ctime()
        for key in self.todays_schedule:
            print '  ' + key
            for show in self.todays_schedule[key]:
                print '    ' + show[0] + ':   ' + show[1]
            print

    def print_events_lists(self):
        print '\nTask list for today'
        for key in self.events:
            print '\n', key
            for show in self.events[key]:
                print show[0], show[1].isoformat()
        print
Example #16
0
class HyperlinkShow:
    """
        Aimed at touchscreens but can be used for any purpose where the user is required to follow hyperlinks between tracks
        Profiles for media tracks (message, image, video, audio ) specify links to other tracks
        in a link a symbolic name of an input is associated with a track-reference
        The show begins at first-track and then uses events (GPIO, keypresses etc.) to link to other tracks via their symbolic names
        If using 'call' keeps a record of the tracks it has visited so the 'return' command can go back.
        Executes timeout-track if no user input is received.

        links are of the form:
           symbolic-name command [track-ref]
        
        link commands:
          call <track-ref> play track-ref and add it to the path
          return - return 1 back up the path removing the track from the path, stops at home-track.
          return n - return n tracks back up the path removing the track from the path, stops at home-track.
          return <track-ref> return to <track-ref> removing tracks from the path
          home  - return to home-track removing tracks from the path
          jump <track-ref-> - play trck-ref forgetting the path back to home-track
          goto <track-ref> - play track-ref, forget the path 
          exit - end the hyperlink show
          null - inhibits the link defined in the show with the same symbolic name.

          reserved symbolic names
          pp-onend command  - pseudo symbolic name for end of a track

        interface:
         * play - selects the first track to play (first-track) 
         * input_pressed,  - receives user events passes them to a Shower/Player if a track is playing,
                otherwise actions them depending on the symbolic name supplied
    """

    # *********************
    # external interface
    # ********************

    def __init__(self, show_params, root, canvas, showlist, pp_dir, pp_home,
                 pp_profile):
        """ canvas - the canvas that the tracks of the event show are to be written on
            show_params - the name of the configuration dictionary section for the hyperlinkshow
            showlist  - the showlist, to enable runningnof show type tracks.
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon = Monitor()
        self.mon.on()

        #instantiate arguments
        self.show_params = show_params
        self.root = root
        self.showlist = showlist
        self.canvas = canvas
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # open resources
        self.rr = ResourceReader()

        #create a path stack
        self.path = PathManager()

        # init variables
        self.drawn = None
        self.player = None
        self.shower = None
        self.timeout_running = None
        self.error = False

    def play(self,
             show_id,
             end_callback,
             ready_callback,
             top=False,
             command='nil'):
        """ starts the hyperlink show at start-track 
              end_callback - function to be called when the show exits
              ready_callback - callback when event-show is ready to display its forst track (not used?)
              top is True when the show is top level (run from [start] or from show control)
              command is not used
        """

        #instantiate arguments
        self.show_id = show_id
        self.end_callback = end_callback
        self.ready_callback = ready_callback
        self.top = top
        self.command = command

        # check data files are available.
        self.medialist_file = self.pp_profile + "/" + self.show_params[
            'medialist']
        if not os.path.exists(self.medialist_file):
            self.mon.err(self,
                         "Medialist file not found: " + self.medialist_file)
            self.end('error', "Medialist file not found")

        #create a medialist object for the hyperlinkshow and read the file into it.
        self.medialist = MediaList()
        if self.medialist.open_list(self.medialist_file,
                                    self.showlist.sissue()) == False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self.end('error', "Version of medialist different to Pi Presents")

        #get controls for this show if top level
        controlsmanager = ControlsManager()
        if self.top == True:
            self.controls_list = controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list = controlsmanager.merge_show_controls(
                self.controls_list, self.show_params['controls'])

        # read show  links and destinations
        self.first_track_ref = self.show_params['first-track-ref']
        self.home_track_ref = self.show_params['home-track-ref']
        self.timeout_track_ref = self.show_params['timeout-track-ref']

        # state variables and signals
        self.end_hyperlinkshow_signal = False
        self.egg_timer = None
        self.next_track_signal = False
        self.next_track_ref = ''
        self.current_track_ref = ''
        self.current_track_type = ''

        # ready callback for show
        if self.ready_callback <> None:
            self.ready_callback()

        self.canvas.delete('pp-content')
        self.canvas.config(bg='black')

        self.do_first_track()

#stop received from another concurrent show via ShowManager

    def managed_stop(self):
        # set signal to stop the hyperlinkshow when all  sub-shows and players have ended
        self.end_hyperlinkshow_signal = True
        # then stop and shows or tracks.
        if self.shower <> None:
            self.shower.managed_stop()
        elif self.player <> None:
            self.player.input_pressed('stop')
        else:
            self.end('normal', 'stopped by ShowManager')

    # kill or error
    def terminate(self, reason):
        self.end_hyperlinkshow_signal = True
        if self.shower <> None:
            self.shower.terminate(reason)
        elif self.player <> None:
            self.player.terminate(reason)
        else:
            self.end(reason, 'terminated without terminating shower or player')

# respond to inputs

    def input_pressed(self, symbol, edge, source):

        self.mon.log(self, "received symbol: " + symbol)

        #does the symbol match a link, if so execute it
        if self.is_link(symbol, edge, source) == True:
            return

        # controls are disabled so ignore anything else
        if self.show_params['disable-controls'] == 'yes':
            return

        # does it match a control
        # if at top convert symbolic name to operation otherwise lower down we have received an operatio
        # look through list of controls to find match
        if self.top == True:
            operation = self.lookup_control(symbol, self.controls_list)
        else:
            operation = symbol
        # print 'operation',operation
        if operation <> '':
            self.do_operation(operation, edge, source)

    def do_operation(self, operation, edge, source):
        if self.shower <> None:
            # if next lower show is running pass down to stop the show and lower level
            self.shower.input_pressed(operation, edge, source)
        else:
            # control this show and its tracks
            if operation == 'stop':
                if self.player <> None:
                    if self.current_track_ref == self.first_track_ref and self.top == False:
                        self.end_radiobuttonshow_signal = True
                    self.player.input_pressed('stop')

            elif operation == 'pause':
                if self.player <> None:
                    self.player.input_pressed(operation)

            elif operation[0:4] == 'omx-' or operation[
                    0:6] == 'mplay-' or operation[0:5] == 'uzbl-':
                if self.player <> None:
                    self.player.input_pressed(operation)

    def is_link(self, symbol, edge, source):
        # find the first entry in links that matches the symbol and execute its operation
        # print 'hyperlinkshow ',symbol
        found = False
        for link in self.links:
            #print link
            if symbol == link[0]:
                found = True
                if link[1] <> 'null':
                    # print 'match',link[0]
                    link_operation = link[1]
                    if link_operation == 'home':
                        self.do_home(edge, source)
                    elif link_operation == 'return':
                        self.do_return(link[2], edge, source)
                    elif link_operation == 'call':
                        self.do_call(link[2], edge, source)
                    elif link_operation == 'goto':
                        self.do_goto(link[2], edge, source)
                    elif link_operation == 'jump':
                        self.do_jump(link[2], edge, source)
                    elif link_operation == 'exit':
                        self.end('normal', 'executed exit command')
        return found

    def lookup_control(self, symbol, controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''

# *********************
# INTERNAL FUNCTIONS
# ********************

# *********************
# Show Sequencer
# *********************

    def timeout_callback(self):
        self.do_call(self.timeout_track_ref, 'front', 'timeout')

    def do_call(self, track_ref, edge, source):
        if track_ref <> self.current_track_ref:
            self.mon.log(self, 'call: ' + track_ref)
            self.next_track_signal = True
            self.next_track_op = 'call'
            self.next_track_arg = track_ref
            if self.shower <> None:
                self.shower.input_pressed('stop', edge, source)
            elif self.player <> None:
                self.player.input_pressed('stop')
            else:
                self.what_next()

    def do_goto(self, to, edge, source):
        if to <> self.current_track_ref:
            self.mon.log(self, 'goto: ' + to)
            self.next_track_signal = True
            self.next_track_op = 'goto'
            self.next_track_arg = to
            if self.shower <> None:
                self.shower.input_pressed('stop', edge, source)
            elif self.player <> None:
                self.player.input_pressed('stop')
            else:
                self.what_next()

    def do_jump(self, to, edge, source):
        if to <> self.current_track_ref:
            self.mon.log(self, 'jump to: ' + to)
            self.next_track_signal = True
            self.next_track_op = 'jump'
            self.next_track_arg = to
            if self.shower <> None:
                self.shower.input_pressed('stop', edge, source)
            elif self.player <> None:
                self.player.input_pressed('stop')
            else:
                self.what_next()

    def do_return(self, to, edge, source):
        self.next_track_signal = True
        if to.isdigit() or to == '':
            self.mon.log(self, 'hyperlink command - return by: ' + to)
            self.next_track_op = 'return-by'
            if to == '':
                self.next_track_arg = '1'
            else:
                self.next_track_arg = to
        else:
            self.mon.log(self, 'hyperlink command - return to: ' + to)
            self.next_track_op = 'return-to'
            self.next_track_arg = to
        if self.shower <> None:
            self.shower.input_pressed('stop', edge, source)
        elif self.player <> None:
            self.player.input_pressed('stop')
        else:
            self.what_next()

    def do_home(self, edge, source):
        if self.current_track_ref <> self.home_track_ref:
            self.mon.log(self, 'hyperlink command - home')
            self.next_track_signal = True
            self.next_track_op = 'home'
            if self.shower <> None:
                self.shower.input_pressed('stop', edge, source)
            elif self.player <> None:
                self.player.input_pressed('stop')
            else:
                self.what_next()

    def do_first_track(self):
        index = self.medialist.index_of_track(self.first_track_ref)
        if index >= 0:
            #don't use select the track as not using selected_track in hyperlinkshow
            first_track = self.medialist.track(index)
            self.current_track_ref = first_track['track-ref']
            self.path.append(first_track['track-ref'])
            self.play_selected_track(first_track)
        else:
            self.mon.err(
                self, "first-track not found in medialist: " +
                self.show_params['first-track-ref'])
            self.end('error', "first track not found in medialist")

    def what_next(self):
        # user wants to end the show
        if self.end_hyperlinkshow_signal == True:
            self.end_hyperlinkshow_signal = False
            self.end('normal', "show ended by user")

        # user has selected another track
        elif self.next_track_signal == True:
            self.next_track_signal = False

            # home
            if self.next_track_op in ('home'):
                # back to 1 before home
                back_ref = self.path.back_to(self.home_track_ref)
                if back_ref == '':
                    self.mon.err(
                        self, "home - home track not in path: " +
                        self.home_track_ref)
                    self.end('error', "home - home track not in path")
                # play home
                self.next_track_ref = self.home_track_ref
                self.path.append(self.next_track_ref)

            # return-by
            elif self.next_track_op in ('return-by'):
                if self.current_track_ref <> self.home_track_ref:
                    # back n stopping at home
                    # back one more and return it
                    back_ref = self.path.back_by(self.home_track_ref,
                                                 self.next_track_arg)
                    # use returned track
                    self.next_track_ref = back_ref
                    self.path.append(self.next_track_ref)

            # return-to
            elif self.next_track_op in ('return-to'):
                #back to one before return-to track
                back_ref = self.path.back_to(self.next_track_arg)
                if back_ref == '':
                    self.mon.err(
                        self, "return-to - track not in path: " +
                        self.next_track_arg)
                    self.end('error', "return-to - track not in path")
                # and append the return to track
                self.next_track_ref = self.next_track_arg
                self.path.append(self.next_track_ref)

            # call
            elif self.next_track_op in ('call'):
                # append the required track
                self.path.append(self.next_track_arg)
                self.next_track_ref = self.next_track_arg

            # goto
            elif self.next_track_op in ('goto'):
                self.path.pop_for_sibling()
                ##                    #back to first track and remove it
                ##                    back_ref=self.path.back_to(self.first_track_ref)
                ##                    if back_ref=='':
                ##                        self.mon.err(self,"goto - first track not in path: "+self.first_track_ref)
                ##                        self.end('error',"goto - first track not in path")
                #add the goto track
                self.next_track_ref = self.next_track_arg
                self.path.append(self.next_track_arg)

            # jump
            elif self.next_track_op in ('jump'):
                # back to home and remove it
                back_ref = self.path.back_to(self.home_track_ref)
                if back_ref == '':
                    self.mon.err(
                        self, "jump - home track not in path: " +
                        self.home_track_ref)
                    self.end('error', "jump - track not in path")
                # add back the home track without playing it
                self.path.append(self.home_track_ref)
                # append the jumped to track
                self.next_track_ref = self.next_track_arg
                self.path.append(self.next_track_ref)

            else:
                self.mon.err(
                    self, "unaddressed what next: " + self.next_track_op +
                    ' ' + self.next_track_arg)
                self.end('error', "unaddressed what next")

            self.current_track_ref = self.next_track_ref
            index = self.medialist.index_of_track(self.next_track_ref)
            if index >= 0:
                #don't use select the track as not using selected_track in hyperlinkshow
                next_track = self.medialist.track(index)
                self.play_selected_track(next_track)
            else:
                self.mon.err(
                    self, "next-track not found in medialist: " +
                    self.next_track_ref)
                self.end('error', "next track not found in medialist")

        else:
            #track ends naturally
            #then input pp-onend symbolic name
            self.input_pressed('pp-onend', 'front', 'key')

# *********************
# Dispatching to Players
# *********************

    def page_callback(self):
        # called from a Player when ready to play, merge the links from the track with those from the show
        # and then enable the click areas
        self.delete_eggtimer()
        links_text = self.player.get_links()
        reason, message, track_links = self.path.parse_links(links_text)
        if reason == 'error':
            self.mon.err(self, message + " in page")
            self.end('error', message)
        self.path.merge_links(self.links, track_links)

        # enable the click-area that are in the list of links
        self.enable_click_areas()

    def enable_click_areas(self):
        for link in self.links:
            #print 'trying link ',link[0]
            if not ('key-'
                    in link[0]) and link[1] <> 'null' and link[0] <> 'pp-auto':
                # print 'enabling link ',link[0]
                self.canvas.itemconfig(link[0], state='normal')

    def play_selected_track(self, selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """

        if self.timeout_running <> None:
            self.canvas.after_cancel(self.timeout_running)
            self.timeout_running = None

        # self.canvas.delete(ALL)
        self.display_eggtimer(self.resource('hyperlinkshow', 'm01'))

        self.current_track_type = selected_track['type']

        #read the show links. Track links will be added by ready_callback
        links_text = self.show_params['links']
        reason, message, self.links = self.path.parse_links(links_text)
        if reason == 'error':
            self.mon.err(self, message + " in show")
            self.end('error', message)

        #start timeout for the track if required

        if self.current_track_ref <> self.first_track_ref and int(
                self.show_params['timeout']) <> 0:
            self.timeout_running = self.canvas.after(
                int(self.show_params['timeout']) * 1000, self.timeout_callback)

        # dispatch track by type
        self.player = None
        self.shower = None
        track_type = selected_track['type']
        self.mon.log(self, "Track type is: " + track_type)

        if track_type == "video":
            # create a videoplayer
            track_file = self.complete_path(selected_track)
            self.player = VideoPlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.page_callback,
                             enable_menu=False)

        elif track_type == "audio":
            # create a audioplayer
            track_file = self.complete_path(selected_track)
            self.player = AudioPlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.page_callback,
                             enable_menu=False)

        elif track_type == "image":
            track_file = self.complete_path(selected_track)
            self.player = ImagePlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(
                track_file,
                self.showlist,
                self.end_player,
                self.page_callback,
                enable_menu=False,
            )

        elif track_type == "web":
            # create a browser
            track_file = self.complete_path(selected_track)
            self.player = BrowserPlayer(self.show_id, self.root, self.canvas,
                                        self.show_params, selected_track,
                                        self.pp_dir, self.pp_home,
                                        self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.page_callback,
                             enable_menu=False)

        elif track_type == "message":
            # bit odd because MessagePlayer is used internally to display text.
            text = selected_track['text']
            self.player = MessagePlayer(self.show_id, self.root, self.canvas,
                                        self.show_params, selected_track,
                                        self.pp_dir, self.pp_home,
                                        self.pp_profile)
            self.player.play(text,
                             self.showlist,
                             self.end_player,
                             self.page_callback,
                             enable_menu=False)

        elif track_type == "show":
            self.enable_click_areas()
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >= 0:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
            else:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self.end("Unknown show")

            if selected_show['type'] == "mediashow":
                self.shower = MediaShow(selected_show, self.root, self.canvas,
                                        self.showlist, self.pp_dir,
                                        self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "liveshow":
                self.shower = LiveShow(selected_show, self.root, self.canvas,
                                       self.showlist, self.pp_dir,
                                       self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "radiobuttonshow":
                self.shower = RadioButtonShow(selected_show, self.root,
                                              self.canvas, self.showlist,
                                              self.pp_dir, self.pp_home,
                                              self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "hyperlinkshow":
                self.shower = HyperlinkShow(selected_show, self.root,
                                            self.canvas, self.showlist, self,
                                            pp_dir, self.pp_home,
                                            self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "menu":
                self.shower = MenuShow(selected_show, self.root, self.canvas,
                                       self.showlist, self.pp_dir,
                                       self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')
            else:
                self.mon.err(self,
                             "Unknown Show Type: " + selected_show['type'])
                self.end("Unknown show type")

        else:
            self.mon.err(self, "Unknown Track Type: " + track_type)
            self.end("Unknown track type")

    # callback from when player ends
    def end_player(self, reason, message):
        self.mon.log(self, "Returned from player with message: " + message)
        self.player = None
        # this does not seem to change the colour of the polygon
        self.canvas.itemconfig('pp-click-area', state='hidden')
        self.canvas.update_idletasks()
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            self.display_eggtimer(self.resource('hyperlinkshow', 'm02'))
            self.what_next()

    # callback from when shower ends
    def end_shower(self, show_id, reason, message):
        self.mon.log(self, "Returned from shower with message: " + message)
        self.shower = None
        self.canvas.itemconfig('pp-click-area', state='hidden')
        self.canvas.update_idletasks()
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            self.display_eggtimer(self.resource('hyperlinkshow', 'm03'))
            self.what_next()

# *********************
# End the show
# *********************
# finish the player for killing, error or normally
# this may be called directly sub/child shows or players are not running
# if they might be running then need to call terminate.

    def end(self, reason, message):
        self.mon.log(self,
                     "Ending hyperlinkshow: " + self.show_params['show-ref'])
        self.end_callback(self.show_id, reason, message)
        self = None
        return

# *********************
# displaying things
# *********************

    def display_eggtimer(self, text):
        #self.egg_timer=self.canvas.create_text(int(self.canvas['width'])/2,
        #int(self.canvas['height'])/2,
        #text= text,
        # fill='white',
        # font="Helvetica 20 bold")
        #self.canvas.update_idletasks( )
        pass

    def delete_eggtimer(self):
        if self.egg_timer != None:
            self.canvas.delete(self.egg_timer)


# *********************
# utilities
# *********************

    def complete_path(self, selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file <> '' and track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Track to play is: " + track_file)
        return track_file

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            # players or showers may be running so need terminate
            self.terminate("error")
        else:
            return value
class MediaList:
    """
    manages a media list of tracks and the track selected from the medialist
    """
    

    def __init__(self):
        self.clear()
        self.mon=Monitor()
        self.mon.on()
        
 # Functions for the editor dealing with complete list

    def clear(self):
        self._tracks = []  #MediaList, stored as a list of dicts
        self._num_tracks=0
        self._selected_track_index=-1 # index of currently selected track

    def print_list(self):
        print '\n'
        print self._tracks

    def first(self):
        self.select(0)

    def length(self):
        return self._num_tracks

    def append(self, track_dict):
        print '\ntrack dict',track_dict
        """appends a track dictionary to the end of the medialist store"""
        self._tracks.append(copy.deepcopy(track_dict))
        self._num_tracks+=1

    def update(self,index,values):
        self._tracks[index].update(values)


    def remove(self,index):
        self._tracks.pop(index)
        self._num_tracks-=1
        # deselect any track, saves worrying about whether index needs changing
        self._selected_track_index=-1

    def move_up(self):
        if self._selected_track_index<>0:
            self._tracks.insert(self._selected_track_index-1, self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index-1)

    def move_down(self):
        if self._selected_track_index<>self._num_tracks-1:
            self._tracks.insert(self._selected_track_index+1, self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index+1)

    def replace(self,index,replacement):
        self._tracks[index]= replacement     
        
        
# Common functions work for anything
           

    def track_is_selected(self):
            if self._selected_track_index>=0:
                return True
            else:
                return False
            
    def selected_track_index(self):
        return self._selected_track_index

    def track(self,index):
        return self._tracks[index]
    
    def selected_track(self):
        """returns a dictionary containing all fields in the selected track """
        return self._selected_track

    def select(self,index):
        """does housekeeping necessary when a track is selected"""
        if self._num_tracks>0 and index>=0 and index< self._num_tracks:
            self._selected_track_index=index
            self._selected_track = self._tracks[index]
            return True
        else:
            return False

# Dealing with anonymous tracks for use and display

  
     
    def at_end(self):
        # true is selected track is last anon
        index=self._num_tracks-1
        while index>=0:
            if self._tracks[index] ['track-ref'] =="":
                end=index
                if self._selected_track_index==end:
                    return True
                else:
                    return False
            index -=1
        return False
        
        
    def index_of_end(self):
        index=self._num_tracks-1
        while index >= 0:
            if self._tracks[index] ['track-ref'] =="":
                return index
            index -=1
        return -1
   
   
    def at_start(self):
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                start =  index
                if self._selected_track_index==start:
                    return True
                else:
                    return False
            index +=1
        return False
   
            
    def index_of_start(self):
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                return index
            index +=1
        return False


    def display_length(self):
        count=0
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                count+=1
            index +=1
        return count

    def start(self):
        # select first anymous track in the list
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True
            index +=1
        return False

    def finish(self):
        # select first anymous track in the list
        index=self._num_tracks-1
        while index>=0:
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True
            index -=1
        return False

    def next(self,sequence):
        if sequence=='ordered':
            if self._selected_track_index== self._num_tracks-1:
                index=0
            else:
                index= self._selected_track_index+1
                
            end=self._selected_track_index
        else:
            index=random.randint(0,self._num_tracks-1)
            if index==0:
                end=self._num_tracks-1
            else:
                end=index-1
         #search for next anonymous track
        # print 'index', index, 'end',end
        while index<>end:
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True
            if index== self._num_tracks-1:
                index=0
            else:
                index= index+1
        return False

    def previous(self,sequence):
        if sequence=='ordered':
            if self._selected_track_index == 0:
                index=self._num_tracks-1
            else:
                index= self._selected_track_index-1
            end = self._selected_track_index
        else:
            index=random.randint(0,self._num_tracks-1)
            if index==self._num_tracks-1:
                end=0
            else:
                end=index+1
        # print 'index', index, 'end',end                
        #search for previous anonymous track            
        while index<>end :
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True                
            if index == 0:
                index=self._num_tracks-1
            else:
                index= index-1
        return False
    
    
# Lookup for labelled tracks
    
    
    def index_of_track(self,wanted_track):
        index = 0
        for track in self._tracks:
            if track['track-ref']==wanted_track:
                return index
            index +=1
        return -1


# open and save


    def open_list(self,filename,showlist_issue):
        """
        opens a saved medialist
        medialists are stored as json arrays.
        """
        ifile  = open(filename, 'rb')
        mdict = json.load(ifile)
        ifile.close()
        self._tracks = mdict['tracks']
        if 'issue' in mdict:
            self.issue= mdict['issue']
        else:
            self.issue="1.0"
        if self.issue==showlist_issue:
            self._num_tracks=len(self._tracks)
            self._selected_track_index=-1
            return True
        else:
            return False

    def issue(self):
        return self.issue

    def save_list(self,filename):
        """ save a medialist """
        if filename=="":
            return False
        dic={'issue':self.issue,'tracks':self._tracks}
        filename=str(filename)
        filename = string.replace(filename,'\\','/')
        tries = 1
        while tries<=10:
            # print "save  medialist  ",filename
            try:
                ofile  = open(filename, "wb")
                json.dump(dic,ofile,sort_keys=True,indent=1)
                ofile.close()
                self.mon.log(self,"Saved medialist "+ filename)
                break
            except IOError:
                self.mon.err(self,"failed to save medialist, trying again " + str(tries))
                tries+=1
        return


# for the future

    def open_csv(self,filename):
        """
        opens a saved csv medialist
        """
        if filename !="" and os.path.exists(filename):
            ifile  = open(filename, 'rb')
            pl=csv.reader(ifile)
            for pl_row in pl:
                if len(pl_row) != 0:
                    entry=dict([('type',pl_row[2]),('location',pl_row[0]),('title',pl_row[1])])
                    self.append(copy.deepcopy(entry))
            ifile.close()
            return True
        else:
            return False
Example #18
0
class ImagePlayer:
    """ Displays an image on a canvas for a period of time. Image display can be interrupted
          Implements animation of transitions but Pi is too slow without GPU aceleration."""

    # slide state constants
    NO_SLIDE = 0
    SLIDE_IN = 1
    SLIDE_DWELL= 2
    SLIDE_OUT= 3

# *******************
# external commands
# *******************

    def __init__(self,canvas,cd,track_params):
        """
                canvas - the canvas onto which the image is to be drawn
                cd -  dictionary of show parameters
                track_params - disctionary of track paramters
        """

        self.mon=Monitor()
        self.mon.on()

        self.canvas=canvas
        self.cd=cd
        self.track_params=track_params

        # open resources
        self.rr=ResourceReader()

        # get config from medialist if there.
        if 'duration' in self.track_params and self.track_params['duration']<>"":
            self.duration= int(self.track_params['duration'])
        else:
            self.duration= int(self.cd['duration'])
            
        if 'transition' in self.track_params and self.track_params['transition']<>"":
            self.transition= self.track_params['transition']
        else:
            self.transition= self.cd['transition']
  
        # keep dwell and porch as an integer multiple of tick          
        self.porch = 1000 #length of pre and post porches for an image (milliseconds)
        self.tick = 100 # tick time for image display (milliseconds)
        self.dwell = (1000*self.duration)- (2*self.porch)
        if self.dwell<0: self.dwell=0

        self.centre_x = int(self.canvas['width'])/2
        self.centre_y = int(self.canvas['height'])/2


    def play(self,
                    track,
                    end_callback,
                    ready_callback,
                    enable_menu=False,
                    starting_callback=None,
                    playing_callback=None,
                    ending_callback=None):
                        
        # instantiate arguments
        self.track=track
        self.enable_menu=enable_menu
        self.ready_callback=ready_callback
        self.end_callback=end_callback

        #init state and signals
        self.state=ImagePlayer.NO_SLIDE
        self.quit_signal=False
        self.kill_required_signal=False
        self.error=False
        self._tick_timer=None
        self.drawn=None
        self.paused=False
        self.pause_text=None

        if os.path.exists(self.track)==True:
            self.pil_image=PIL.Image.open(self.track)
            # adjust brightness and rotate (experimental)
            # pil_image_enhancer=PIL.ImageEnhance.Brightness(pil_image)
            # pil_image=pil_image_enhancer.enhance(0.1)
            # pil_image=pil_image.rotate(45)
            # tk_image = PIL.ImageTk.PhotoImage(pil_image)

            # resize image to fit canvas
            canvas_width = self.canvas.winfo_width()
            canvas_height = self.canvas.winfo_height()
            if int(canvas_width) == 1 and int(canvas_height) == 1:
                canvas_width = self.canvas.winfo_reqwidth()
                canvas_height = self.canvas.winfo_reqheight()
            image_ratio = float(self.pil_image.size[0]) / float(self.pil_image.size[1])
            canvas_ratio = float(canvas_width) / float(canvas_height)
            if image_ratio < canvas_ratio:
                image_height = canvas_height
                image_width = self.pil_image.size[0] * canvas_height / self.pil_image.size[1]
            elif image_ratio > canvas_ratio:
                image_width = canvas_width
                image_height = self.pil_image.size[1] * canvas_width / self.pil_image.size[0]
            else:
                image_width = canvas_width
                image_height = canvas_height
            self.pil_image = self.pil_image.resize((int(image_width), int(image_height)), PIL.Image.ANTIALIAS)
        else:
            self.pil_image=None
            # display 'Out of Order' for 7 seconds
            self.dwell = (1000*7)- (2*self.porch)
            if self.dwell<0: self.dwell=0
            
        # and start image rendering
        self._start_front_porch()

        
    def key_pressed(self,key_name):
        if key_name=='':
            return
        elif key_name in ('p',' '):
            self.pause()
        elif key_name=='escape':
            self._stop()
            return

    def button_pressed(self,button,edge):
        if button =='pause':
            self.pause()
        elif button=='stop':
            self._stop()
            return

    def terminate(self,reason):
        if reason=='error':
            self.error=True
            self.quit_signal=True
        else:
            self.kill_required_signal=True
            self.quit_signal=True

    def pause(self):
        if not self.paused:
            self.paused = True
        else:
            self.paused=False

    
        
# *******************
# internal functions
# *******************

    def _stop(self):
        self.quit_signal=True
        
    def _error(self):
        self.error=True
        self.quit_signal=True
  
     #called when back porch has completed or quit signal is received
    def _end(self,reason,message):
        if self._tick_timer<>None:
            self.canvas.after_cancel(self._tick_timer)
            self._tick_timer=None
        self.quit_signal=False
        # self.canvas.delete(ALL)
        self.canvas.update_idletasks( )
        self.state=self.NO_SLIDE
        if self.error==True:
            self.end_callback("error",message)
            self=None          
        elif self.kill_required_signal==True:
            self.end_callback("killed",message)
            self=None           
        else:
            self.end_callback(reason,message)
            self=None


    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self._error()
        else:
            return value



    def _start_front_porch(self):
        self.state=ImagePlayer.SLIDE_IN
        self.porch_counter=0
        if self.ready_callback<>None: self.ready_callback()

        if self.transition=="cut":
            #just display the slide full brightness. No need for porch but used for symmetry
            if self.pil_image<>None:
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                      image=self.tk_img, anchor=CENTER)

  
        elif self.transition=="fade":
            #experimental start black and increase brightness (controlled by porch_counter).
            self._display_image()

        elif self.transition == "slide":
            #experimental, start in middle and move to right (controlled by porch_counter)
            if self.pil_image<>None:
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                  image=self.tk_img, anchor=CENTER)
            
        elif self.transition=="crop":
            #experimental, start in middle and crop from right (controlled by porch_counter)
            if self.pil_image<>None:
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                  image=self.tk_img, anchor=CENTER)

        self._tick_timer=self.canvas.after(self.tick, self._do_front_porch)
        
            
    def _do_front_porch(self):
        if self.quit_signal == True:
            self._end('normal','user quit')
        else:
            self.porch_counter=self.porch_counter+1
            # print "doing slide front porch " +str(self.porch_counter)
            self._display_image()
            if self.porch_counter==self.porch/self.tick:
                self._start_dwell()
            else:
                self._tick_timer=self.canvas.after(self.tick,self._do_front_porch)


    def _start_dwell(self):
        self.state=ImagePlayer.SLIDE_DWELL
        self.dwell_counter=0
        self._tick_timer=self.canvas.after(self.tick, self._do_dwell)

        
    def _do_dwell(self):
        if self.quit_signal == True:
            self.mon.log(self,"quit received")
            self._end('normal','user quit')
        else:
            if self.paused == False:
                self.dwell_counter=self.dwell_counter+1

            # one time flipping of pause text
            if self.paused==True and self.pause_text==None:
                self.pause_text=self.canvas.create_text(100,100, anchor=NW,
                                                      text=self.resource('imageplayer','m01'),
                                                      fill="white",
                                                      font="arial 25 bold")
                self.canvas.update_idletasks( )
                
            if self.paused==False and self.pause_text<>None:
                    self.canvas.delete(self.pause_text)
                    self.pause_text=None
                    self.canvas.update_idletasks( )

            if self.dwell_counter==self.dwell/self.tick:
                self._start_back_porch()
            else:
                self._tick_timer=self.canvas.after(self.tick, self._do_dwell)

    def _start_back_porch(self):
        self.state=ImagePlayer.SLIDE_OUT
        self.porch_counter=self.porch/self.tick
        
        if self.transition=="cut":
             # just keep displaying the slide full brightness.
            # No need for porch but used for symmetry
             pass
            
        elif self.transition=="fade":
            #experimental start full and decrease brightness (controlled by porch_counter).
            self._display_image()

        elif self.transition== "slide":
            #experimental, start in middle and move to right (controlled by porch_counter)
            if self.pil_image<>None:
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                  image=self.tk_img, anchor=CENTER)
            
        elif self.transition =="crop":
            #experimental, start in middle and crop from right (controlled by porch_counter)
            if self.pil_image<>None:
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                  image=self.tk_img, anchor=CENTER)

        self._tick_timer=self.canvas.after(self.tick, self._do_back_porch)

            
    def _do_back_porch(self):
        if self.quit_signal == True:
            self._end('normal','user quit')
        else:
            self.porch_counter=self.porch_counter-1
            self._display_image()
            if self.porch_counter==0:
                self._end('normal','finished')
            else:
                self._tick_timer=self.canvas.after(self.tick,self._do_back_porch)

    



    def _display_image(self):
        if self.transition=="cut":
            pass
        
        # all the methods below have incorrect code !!!
        elif self.transition=="fade":
            if self.pil_image<>None:
                self.enh=PIL.ImageEnhance.Brightness(self.pil_image)
                prop=float(self.porch_counter)/float(20)  #????????
                self.pil_img=self.enh.enhance(prop)
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_img)
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                      image=self.tk_img, anchor=CENTER)

        elif self.transition=="slide":
            if self.pil_image<>None:
                self.canvas.move(self.drawn,5,0)
            
        elif self.transition=="crop":
            if self.pil_image<>None:
                self.crop= 10*self.porch_counter
                self.pil_img=self.pil_image.crop((0,0,1000-self.crop,1080))
                self.tk_img=PIL.ImageTk.PhotoImage(self.pil_img)           
                self.drawn = self.canvas.create_image(self.centre_x, self.centre_y,
                                                      image=self.tk_img, anchor=CENTER)

        # display message if image is not available
                
        if self.pil_image== None:
            self.canvas.create_text(self.centre_x, self.centre_y,
                                                  text=self.resource('imageplayer','m02'),
                                                  fill='white',
                                                font='arial 30 bold')

        # display instructions if enabled
       
        if self.enable_menu== True:
            self.canvas.create_text(self.centre_x, int(self.canvas['height']) - int(self.cd['hint-y']),
                                                  text=self.cd['hint-text'],
                                                  fill=self.cd['hint-colour'],
                                                font=self.cd['hint-font'])

        # display show text if enabled
        if self.cd['show-text']<> '':
            self.canvas.create_text(int(self.cd['show-text-x']),int(self.cd['show-text-y']),
                                                    anchor=NW,
                                                  text=self.cd['show-text'],
                                                  fill=self.cd['show-text-colour'],
                                                  font=self.cd['show-text-font'])
            
        # display track text if enabled
        if self.track_params['track-text']<> '':
            self.canvas.create_text(int(self.track_params['track-text-x']),int(self.track_params['track-text-y']),
                                                    anchor=NW,
                                                  text=self.track_params['track-text'],
                                                  fill=self.track_params['track-text-colour'],
                                                  font=self.track_params['track-text-font'])
            
        self.canvas.update_idletasks( )
Example #19
0
class Player(object):

    # common bits of __init__(...)
    def __init__(self,
                 show_id,
                 showlist,
                 root,
                 canvas,
                 show_params,
                 track_params,
                 pp_dir,
                 pp_home,
                 pp_profile,
                 end_callback,
                 command_callback):

        # create debugging log object
        self.mon=Monitor()

        self.mon.trace(self,'')

        # instantiate arguments
        self.show_id=show_id
        self.showlist=showlist
        self.root=root
        self.canvas=canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height= canvas['show-canvas-height']
        self.show_canvas_centre_x= canvas['show-canvas-centre-x']
        self.show_canvas_centre_y= canvas['show-canvas-centre-y']
        self.show_params=show_params
        self.track_params=track_params
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile
        self.end_callback=end_callback
        self.command_callback=command_callback
                
        # get background image from profile.
        self.background_file=''
        if self.track_params['background-image'] != '':
            self.background_file= self.track_params['background-image']

            
        # get background colour from profile.
        if self.track_params['background-colour'] != '':
            self.background_colour= self.track_params['background-colour']
        else:
            self.background_colour= self.show_params['background-colour']
        
        # get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim=PluginManager(self.show_id,self.root,self.canvas,self.show_params,self.track_params,self.pp_dir,self.pp_home,self.pp_profile) 

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj=None
        self.show_text_obj=None
        self.track_text_obj=None
        self.hint_obj=None
        self.background=None
        self.freeze_at_end_required='no' # overriden by videoplayer
        self.tick_timer=None
        self.terminate_signal=False
        self.play_state=''



    def pre_load(self):

        pass

              
    # common bits of show(....) 
    def pre_show(self):
        self.mon.trace(self,'')

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()
  
        #ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)


        # Control other shows and do counters and osc at beginning
        self.show_control(self.track_params['show-control-begin'])

        # and show whatever the plugin has created
        self.show_plugin()
        
        # create animation events
        reason,message=self.animate.animate(self.animate_begin_text,id(self))
        if reason  ==  'error':
            self.mon.err(self,message)
            self.play_state='show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error',message)
        else:
            # return to start playing the track.
            self.mon.log(self,">show track received from show Id: "+ str(self.show_id))
            return


    # to keep landscape happy
    def ready_callback(self,enable_show_background):
        self.mon.fatal(self,'ready callback not overridden')
        self.end('error','ready callback not overridden')

    def finished_callback(self,reason,message):
        self.mon.fatal(self,'finished callback not overridden')
        self.end('error','finished callback not overridden')

    def closed_callback(self,reason,message):
        self.mon.fatal(self,'closed callback not overridden')
        self.end('error','closed callback not overridden')


# Control shows so pass the show control commands back to PiPresents via the command callback
    def show_control(self,show_control_text): 
        lines = show_control_text.split('\n')
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line, source='track',show=self.show_params['show-ref'])



# *****************
# hide content and end animation, show control etc.
# called by ready calback and end
# *****************

    def hide(self):
        self.mon.trace(self,'')
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer=None
        
        self.hide_x_content()
        
        # stop the plugin
        self.hide_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params['show-control-end'])
        
        # clear events list for this track
        if self.track_params['animate-clear'] == 'yes':
            self.animate.clear_events_list(id(self))
                
        # create animation events for ending
        # !!!!! TEMPORARY FIX
        reason,message=self.animate.animate(self.animate_end_text,id(self))
        if reason == 'error':
            self.mon.err(self,message)
            # self.play_state='show-failed'
            # if self.finished_callback is not None:
                # self.finished_callback('error',message)
        else:
            return


    def terminate(self):
        self.mon.trace(self,'')
        self.terminate_signal=True
        if self.play_state == 'showing':
            # call the derived class's stop method
            self.stop()
        else:
            self.end('killed','terminate with no track or show open')

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self,'stop not overidden by derived class')
        self.play_state='show-failed'
        if self.finished_callback is not None:
            self.finished_callback('error','stop not overidden by derived class')


    def get_play_state(self):
        return self.play_state
  
# *****************
# ending the player
# *****************

    def end(self,reason,message):
        self.mon.trace(self,'')
        # stop the plugin

        if self.terminate_signal is True:
            reason='killed'
            self.terminate_signal=False
            self.hide()

        self.end_callback(reason,message)
        self=None


# *****************
# displaying common things
# *****************

    def load_plugin(self):
        # called in load before load_x_content modify the track here
        if self.track_params['plugin'] != '':
            reason,message,self.track = self.pim.load_plugin(self.track,self.track_params['plugin'])
            return reason,message
        
    def show_plugin(self):
        # called at show time, write to the track here if you need it after show_control_begin (counters)
        if self.track_params['plugin'] != '':
            self.pim.show_plugin()

    def hide_plugin(self):
        # called at the end of the track
        if self.track_params['plugin'] != '':
            self.pim.stop_plugin()

    def load_x_content(self,enable_menu):
        self.mon.trace(self,'')
        self.background_obj=None
        self.background=None
        self.track_text_obj=None
        self.show_text_obj=None
        self.hint_obj=None
        self.track_obj=None

        
        # background image
        if self.background_file != '':
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error',"Track background file not found "+ background_img_file
            else:
                try:
                    pil_background_img=Image.open(background_img_file)
                except:
                    pil_background_img=None
                    self.background=None
                    self.background_obj=None
                    return 'error','Track background, not a recognised image format '+ background_img_file                
                # print 'pil_background_img ',pil_background_img
                image_width,image_height=pil_background_img.size
                window_width=self.show_canvas_width
                window_height=self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img=pil_background_img.resize((window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(self.show_canvas_x1,
                                                               self.show_canvas_y1,
                                                               image=self.background,
                                                               anchor=NW)
                # print '\nloaded background_obj: ',self.background_obj


        # load the track content.  Dummy function below is overridden in players          
        status,message=self.load_track_content()
        if status == 'error':
            return 'error',message
                          
        # load show text if enabled
        if self.show_params['show-text'] !=  '' and self.track_params['display-show-text'] == 'yes':

            x,y,anchor,justify=calculate_text_position(self.show_params['show-text-x'],self.show_params['show-text-y'],
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,self.show_params['show-text-justify'])
 
            self.show_text_obj=self.canvas.create_text(x,y,
                                                       anchor=anchor,
                                                       justify=justify,
                                                       text=self.show_params['show-text'],
                                                       fill=self.show_params['show-text-colour'],
                                                       font=self.show_params['show-text-font'])


        # load track text if enabled

        if self.track_params['track-text-x'] =='':
            track_text_x= self.show_params['track-text-x']
        else:
            track_text_x= self.track_params['track-text-x']


        if self.track_params['track-text-y'] =='':
            track_text_y= self.show_params['track-text-y']
        else:
            track_text_y= self.track_params['track-text-y']

        if self.track_params['track-text-justify'] =='':
            track_text_justify= self.show_params['track-text-justify']
        else:
            track_text_justify= self.track_params['track-text-justify']

        if self.track_params['track-text-font'] =='':
            track_text_font= self.show_params['track-text-font']
        else:
            track_text_font= self.track_params['track-text-font']
            

        if self.track_params['track-text-colour'] =='':
            track_text_colour= self.show_params['track-text-colour']
        else:
            track_text_colour= self.track_params['track-text-colour']
            
            
        if self.track_params['track-text'] !=  '':

            x,y,anchor,justify=calculate_text_position(track_text_x,track_text_y,
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,track_text_justify)
 
            
            self.track_text_obj=self.canvas.create_text(x,y,
                                                        anchor=anchor,
                                                        justify=justify,
                                                        text=self.track_params['track-text'],
                                                        fill=track_text_colour,
                                                        font=track_text_font)

        # load instructions if enabled
        if enable_menu is  True:

            x,y,anchor,justify=calculate_text_position(self.show_params['hint-x'],self.show_params['hint-y'],
                                     self.show_canvas_x1,self.show_canvas_y1,
                                     self.show_canvas_centre_x,self.show_canvas_centre_y,
                                     self.show_canvas_x2,self.show_canvas_y2,self.show_params['hint-justify'])
 

            self.hint_obj=self.canvas.create_text(x,y,
                                                  justify=justify,
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                  font=self.show_params['hint-font'],
                                                  anchor=anchor)

        self.display_show_canvas_rectangle()

        self.canvas.tag_raise('pp-click-area')
        self.canvas.itemconfig(self.background_obj,state='hidden')
        self.canvas.itemconfig(self.show_text_obj,state='hidden')
        self.canvas.itemconfig(self.track_text_obj,state='hidden')
        self.canvas.itemconfig(self.hint_obj,state='hidden')
        self.canvas.update_idletasks( )
        return 'normal','x-content loaded'


    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass


    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer
        
    def load_track_content(self):
        return 'normal','player has no track content to load'

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self,'')
        # background colour
        if  self.background_colour != '':
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj,state='normal')
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj,state='normal')
        self.canvas.itemconfig(self.track_text_obj,state='normal')
        self.canvas.itemconfig(self.hint_obj,state='normal')
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params['display-show-background']=='yes':
            self.enable_show_background=True
        else:
            self.enable_show_background=False
        # print 'ENABLE SB',self.enable_show_background


    def hide_x_content(self):
        self.mon.trace(self,'')
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj,state='hidden')
        self.canvas.itemconfig(self.show_text_obj,state='hidden')
        self.canvas.itemconfig(self.track_text_obj,state='hidden')
        self.canvas.itemconfig(self.hint_obj,state='hidden')
        # self.canvas.update_idletasks( )
        
        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background=None
        # self.canvas.update_idletasks( )


# ****************
# utilities
# *****************

    def get_links(self):
        return self.track_params['links']

    # produce an absolute path from the relative one in track paramters
    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file=self.pp_home+track_file[1:]
        elif track_file[0] == "@":
            track_file=self.pp_profile+track_file[1:]
        return track_file
        
    # get a text string from resources.cfg
    def resource(self,section,item):
        value=self.rr.get(section,item)
        return value # False if not found
Example #20
0
class OSCDriver(object):

    # executed by main program
    def  init(self,pp_profile,manager_unit,preferred_interface,my_ip,show_command_callback,input_event_callback,animate_callback):

        self.pp_profile=pp_profile
        self.show_command_callback=show_command_callback
        self.input_event_callback=input_event_callback
        self.animate_callback=animate_callback

        self.mon=Monitor()
        config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self, 'OSC Configuration file not found: '+config_file)
            return'error','OSC Configuration file nof found: '+config_file
        
        self.mon.log(self, 'OSC Configuration file found at: '+config_file)        
        self.osc_config=OSCConfig()
        
        # reads config data 
        if self.osc_config.read(config_file) ==False:
            return 'error','failed to read osc.cfg'

        # unpack config data and initialise

        if self.osc_config.this_unit_name =='':
            return 'error','OSC Config -  This Unit has no name'
        if len(self.osc_config.this_unit_name.split())>1:
            return 'error','OSC config - This Unit Name not a single word: '+self.osc_config.this_unit_name
        self.this_unit_name=self.osc_config.this_unit_name
               
                 
        if self.osc_config.this_unit_ip=='':
            self.this_unit_ip=my_ip
        else:
            self.this_unit_ip=self.osc_config.this_unit_ip

        if self.osc_config.slave_enabled == 'yes':
            if not self.osc_config.listen_port.isdigit():
                return 'error','OSC Config - Listen port is not a positve number: '+ self.osc_config.listen_port
            self.listen_port= self.osc_config.listen_port

        if self.osc_config.master_enabled == 'yes':
            if not self.osc_config.reply_listen_port.isdigit():
                return 'error','OSC Config - Reply Listen port is not a positve number: '+ self.osc_config.reply_listen_port
            self.reply_listen_port= self.osc_config.reply_listen_port

            # prepare the list of slaves
            status,message=self.parse_slaves()
            if status=='error':
                return status,message
             

        self.prefix='/pipresents'
        self.this_unit='/' + self.this_unit_name
        
        self.input_server=None
        self.input_reply_client=None
        self.input_st=None
        
        self.output_client=None
        self.output_reply_server=None
        self.output_reply_st=None


        if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port:
            # The two listen ports are the same so use one server for input and output

            #start the client that sends commands to the slaves
            self.output_client=OSC.OSCClient()
            self.mon.log(self, 'sending commands to slaves and replies to master on: '+self.reply_listen_port)

            #start the input+output reply server
            self.mon.log(self, 'listen to commands and replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port)
            self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client)
            self.add_default_handler(self.output_reply_server)
            self.add_input_handlers(self.output_reply_server)
            self.add_output_reply_handlers(self.output_reply_server)

            self.input_server=self.output_reply_server
            
        else:

            if self.osc_config.slave_enabled == 'yes':
                # we want this to be a slave to something else

                # start the client that sends replies to controlling unit
                self.input_reply_client=OSC.OSCClient()
                
                #start the input server
                self.mon.log(self, 'listening to commands on: ' + self.this_unit_ip+':'+self.listen_port)
                self.input_server=myOSCServer((self.this_unit_ip,int(self.listen_port)),self.input_reply_client)
                self.add_default_handler(self.input_server)
                self.add_input_handlers(self.input_server)
                print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n')
                
            if self.osc_config.master_enabled =='yes':
                #we want to control other units
               
                #start the client that sends commands to the slaves
                self.output_client=OSC.OSCClient()
                self.mon.log(self, 'sending commands to slaves on port: '+self.reply_listen_port)

                #start the output reply server
                self.mon.log(self, 'listen to replies from slave units using: ' + self.this_unit_ip+':'+self.reply_listen_port)
                self.output_reply_server=myOSCServer((self.this_unit_ip,int(self.reply_listen_port)),self.output_client)
                self.add_default_handler(self.output_reply_server)
                self.add_output_reply_handlers(self.output_reply_server)
        
        return 'normal','osc.cfg read'


    def terminate(self):
        if self.input_server != None:
            self.input_server.close()
        if self.output_reply_server != None:
            self.output_reply_server.close()
        self.mon.log(self, 'Waiting for Server threads to finish')
        if self.input_st != None:
            self.input_st.join() ##!!!
        if self.output_reply_st != None:
            self.output_reply_st.join() ##!!!
        self.mon.log(self,'server threads closed')
        if self.input_reply_client !=None:
            self.input_reply_client.close()
        if self.output_client !=None:
            self.output_client.close()


    def start_server(self):
        # Start input Server
        self.mon.log(self,'Starting input OSCServer')
        if self.input_server != None:
            self.input_st = threading.Thread( target = self.input_server.serve_forever )
            self.input_st.start()

        # Start output_reply server
        self.mon.log(self,'Starting output reply OSCServer')
        if self.output_reply_server != None:
            self.output_reply_st = threading.Thread( target = self.output_reply_server.serve_forever )
            self.output_reply_st.start()

    def parse_slaves(self):
        name_list=self.osc_config.slave_units_name.split()
        ip_list=self.osc_config.slave_units_ip.split()
        if len(name_list)==0:
            return 'error','OSC Config - List of slaves name is empty'
        if len(name_list) != len(ip_list):
            return 'error','OSC Config - Lengths of list of slaves name and slaves IP is different'       
        self.slave_name_list=[]
        self.slave_ip_list=[]
        for i, name in enumerate(name_list):
            self.slave_name_list.append(name)
            self.slave_ip_list.append(ip_list[i])
        return 'normal','slaves parsed'


    def parse_osc_command(self,fields):
    # send message to slave unit - INTERFACE WITH pipresents
        if len(fields) <2:
               return 'error','too few fields in OSC command '+' '.join(fields)
        to_unit_name=fields[0]
        show_command=fields[1]
        # print 'FIELDS ',fields
        
        # send an arbitary osc message            
        if show_command == 'send':
            if len(fields)>2:
                osc_address= fields[2]
                arg_list=[]
                if len(fields)>3:
                    arg_list=fields[3:]
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)

        elif show_command in ('open','close','openexclusive'):
            if len(fields)==3:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= [fields[2]]
            else:
                return 'error','OSC - wrong number of fields in '+ ' '.join(fields)
                
        elif show_command =='monitor':
            if fields[2] in ('on','off'):
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list=[fields[2]]
            else:
                self.mon.err(self,'OSC - illegal state in '+ show_command + ' '+fields[2])
        
        elif show_command =='event':                
            if len(fields)==3:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= [fields[2]]


        elif show_command == 'animate':                
            if len(fields)>2:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= fields[2:]
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)
            
        elif show_command in ('closeall','exitpipresents','shutdownnow','reboot'):
            if len(fields)==2:
                osc_address=self.prefix+'/'+ to_unit_name + '/core/'+ show_command
                arg_list= []
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)

        elif show_command in ('loopback','server-info'):
            if len(fields)==2:
                osc_address=self.prefix+'/'+ to_unit_name + '/system/'+ show_command
                arg_list= []
            else:
                return 'error','OSC - wrong nmber of fields in '+ ' '.join(fields)
            
        else:
            return 'error','OSC - unkown command in '+ ' '.join(fields)

        
        ip=self.find_ip(to_unit_name,self.slave_name_list,self.slave_ip_list)
        if ip=='':
            return 'warn','OSC Unit Name not in the list of slaves: '+ to_unit_name
        self.sendto(ip,osc_address,arg_list)
        return 'normal','osc command sent'


    def find_ip(self,name,name_list,ip_list):
        i=0
        for j in name_list:
            if j == name:
                break
            i=i+1
            
        if i==len(name_list):
            return ''
        else:
            return ip_list[i]
                    

    def sendto(self,ip,osc_address,arg_list):
        # print ip,osc_address,arg_list
        if self.output_client is None:
            self.mon.warn(self,'Master not enabled, ignoring OSC command')
            return
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(osc_address)
        for arg in arg_list:
            # print arg
            msg.append(arg)
            
        try:
            self.output_client.sendto(msg,(ip,int(self.reply_listen_port)))
            self.mon.log(self,'Sent OSC command: '+osc_address+' '+' '.join(arg_list) + ' to '+ ip +':'+self.reply_listen_port)
        except Exception as e:
            self.mon.warn(self,'error in client when sending OSC command: '+ str(e))


# **************************************
# Handlers for fallback
# **************************************

    def add_default_handler(self,server):
        server.addMsgHandler('default', self.no_match_handler)

    def no_match_handler(self,addr, tags, stuff, source):
        text= "No handler for message from %s" % OSC.getUrlStr(source)+'\n'
        text+= "     %s" % addr+ self.pretty_list(stuff,'')
        self.mon.warn(self,text)
        return None

# **************************************
# Handlers for Slave (input)
# **************************************    

    def add_input_handlers(self,server):
        server.addMsgHandler(self.prefix + self.this_unit+"/system/server-info", self.server_info_handler)
        server.addMsgHandler(self.prefix + self.this_unit+"/system/loopback", self.loopback_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/openexclusive', self.openexclusive_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/closeall', self.closeall_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/reboot', self.reboot_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/animate', self.animate_handler)
        server.addMsgHandler(self.prefix+ self.this_unit+'/core/monitor', self.monitor_handler)


    # reply to master unit with name of this unit and commands
    def server_info_handler(self,addr, tags, stuff, source):

        msg = OSC.OSCMessage(self.prefix+'/system/server-info-reply')
        msg.append(self.this_unit_name)
        msg.append(self.input_server.getOSCAddressSpace())
        self.mon.log(self,'Sent Server Info reply to %s:' % OSC.getUrlStr(source))
        return msg


    # reply to master unit with a loopback message
    def loopback_handler(self,addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix+'/system/loopback-reply')
        self.mon.log(self,'Sent loopback reply to %s:' % OSC.getUrlStr(source))
        return msg


 
    def open_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('open ',args,1)

    def openexclusive_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('openexclusive ',args,1)
        
    def close_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('close ', args,1)

    def closeall_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('closeall',args,0)

    def monitor_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('monitor ', args,1)

    def exitpipresents_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents',args,0)

    def reboot_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('reboot',args,0)

    def shutdownnow_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow',args,0)

    def prepare_show_command_callback(self,command,args,limit):
        if len(args) == limit:
            if limit !=0:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command + ' ' +args[0])
                self.show_command_callback(command+args[0])
            else:
                self.mon.sched(self,TimeOfDay.now,'Received from OSC: '+ command)
                self.show_command_callback(command)                
        else:
            self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring')  

    def input_event_handler(self,address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0],'OSC')
        else:
            self.mon.warn(self,'OSC input event does not have 1 argument - ignoring')    


    def animate_handler(self,address, tags, args, source):
        if len(args) !=0:
            # delay symbol,param_type,param_values,req_time as a string
            text='0 '
            for arg in args:
                text= text+ arg + ' '
            text = text + '0'
            print text
            self.animate_callback(text)
        else:
            self.mon.warn(self,'OSC output event has no arguments - ignoring')      

# **************************************
# Handlers for Master- replies from slaves (output)
# **************************************

    # reply handlers do not have the destinatuion unit in the address as they are always sent to the originator
    def add_output_reply_handlers(self,server):
        server.addMsgHandler(self.prefix+"/system/server-info-reply", self.server_info_reply_handler)
        server.addMsgHandler(self.prefix+"/system/loopback-reply", self.loopback_reply_handler)
        
        
    # print result of info request from slave unit
    def server_info_reply_handler(self,addr, tags, stuff, source):
        self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n'))
        print 'Received reply to Server-Info command from slave: ',OSC.getUrlStr(source), self.pretty_list(stuff,'\n')
        return None

    #print result of info request from slave unit
    def loopback_reply_handler(self,addr, tags, stuff, source):
        self.mon.log(self,'server info reply from slave '+OSC.getUrlStr(source)+ self.pretty_list(stuff,'\n'))
        print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr(source)+ ' '+ self.pretty_list(stuff,'\n')
        return None


    def pretty_list(self,fields, separator):
        text=' '
        for field in fields:
            text += str(field) + separator
        return text+'\n'
class PiPresents(object):
    def pipresents_version(self):
        vitems = self.pipresents_issue.split('.')
        if len(vitems) == 2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000 * int(vitems[0]) + 100 * int(vitems[1])
        else:
            return 1000 * int(vitems[0]) + 100 * int(vitems[1]) + int(
                vitems[2])

    def __init__(self):
        # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_SAVEALL)
        self.pipresents_issue = "1.4.4"
        self.pipresents_minorissue = '1.4.4a'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45  # proportion of width
        self.nonfull_window_height = 0.7  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner

        StopWatch.global_enable = False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM, self.handle_sigterm)

        # ****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()
        # print (self.options)

        # get Pi Presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            if self.options['manager'] is False:
                tkinter.messagebox.showwarning("Pi Presents",
                                               "Bad Application Directory")
            exit(102)

        # Initialise logging and tracing
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes = [
            'PiPresents', 'HyperlinkShow', 'RadioButtonShow', 'ArtLiveShow',
            'ArtMediaShow', 'MediaShow', 'LiveShow', 'MenuShow', 'GapShow',
            'Show', 'ArtShow', 'AudioPlayer', 'BrowserPlayer', 'ImagePlayer',
            'MenuPlayer', 'MessagePlayer', 'VideoPlayer', 'Player',
            'MediaList', 'LiveList', 'ShowList', 'PathManager',
            'ControlsManager', 'ShowManager', 'PluginManager',
            'IOPluginManager', 'MplayerDriver', 'OMXDriver', 'UZBLDriver',
            'TimeOfDay', 'ScreenDriver', 'Animate', 'OSCDriver',
            'CounterManager', 'BeepsManager', 'Network', 'Mailer'
        ]

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        # Monitor.classes=['OSCDriver']

        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched(
            self, None,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue +
            ' at ' + time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log(
            self,
            "Pi Presents is starting, Version:" + self.pipresents_minorissue +
            ' at ' + time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as ifile:
            self.mon.log(self, '\nRaspbian: ' + ifile.read())

        self.mon.log(
            self,
            '\n' + check_output(["omxplayer", "-v"], universal_newlines=True))
        self.mon.log(
            self,
            '\nGPU Memory: ' + check_output(["vcgencmd", "get_mem", "gpu"],
                                            universal_newlines=True))

        if os.geteuid() == 0:
            print('Do not run Pi Presents with sudo')
            self.mon.log(self, 'Do not run Pi Presents with sudo')
            self.mon.finish()
            sys.exit(102)

        if "DESKTOP_SESSION" not in os.environ:
            print('Pi Presents must be run from the Desktop')
            self.mon.log(self, 'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self, 'Desktop is ' + os.environ['DESKTOP_SESSION'])

        # optional other classes used
        self.root = None
        self.ppio = None
        self.tod = None
        self.animate = None
        self.ioplugin_manager = None
        self.oscdriver = None
        self.osc_enabled = False
        self.tod_enabled = False
        self.email_enabled = False

        user = os.getenv('USER')

        if user is None:
            tkinter.messagebox.showwarning(
                "You must be logged in to run Pi Presents")
            exit(102)

        if user != 'pi':
            self.mon.warn(self, "You must be logged as pi to use GPIO")

        self.mon.log(self, 'User is: ' + user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # check network is available
        self.network_connected = False
        self.network_details = False
        self.interface = ''
        self.ip = ''
        self.unit = ''

        # sets self.network_connected and self.network_details
        self.init_network()

        # start the mailer and send email when PP starts
        self.email_enabled = False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime(
                    "%Y-%m-%d %H:%M")
                message = time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\nUnit: ' + self.unit + '   Profile: ' + self.options[
                    'profile'] + '\n ' + self.interface + '\n ' + self.ip
                self.send_email('start', subject, message)

        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path = "/pp_profiles/" + self.options['profile']
        else:
            self.mon.err(self, "Profile not specified in command ")
            self.end('error',
                     'Profile not specified with the commands -p option')

    # get directory containing pp_home from the command,
        if self.options['home'] == "":
            home = os.sep + 'home' + os.sep + user + os.sep + "pp_home"
        else:
            home = self.options['home'] + os.sep + "pp_home"
        self.mon.log(self, "pp_home directory is: " + home)

        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found = False
        for i in range(1, 10):
            self.mon.log(self,
                         "Trying pp_home at: " + home + " (" + str(i) + ')')
            if os.path.exists(home):
                found = True
                self.pp_home = home
                break
            time.sleep(1)
        if found is True:
            self.mon.log(
                self,
                "Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self, "Failed to find pp_home directory at " + home)
            self.end('error', "Failed to find pp_home directory at " + home)

        # check profile exists
        self.pp_profile = self.pp_home + self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self, None,
                           "Running profile: " + self.pp_profile_path)
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.mon.err(
                self, "Failed to find requested profile: " + self.pp_profile)
            self.end('error',
                     "Failed to find requested profile: " + self.pp_profile)

        self.mon.start_stats(self.options['profile'])

        if self.options['verify'] is True:
            self.mon.err(self,
                         "Validation option not supported - use the editor")
            self.end('error',
                     'Validation option not supported - use the editor')

        # initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', "showlist not found at " + self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(
                self,
                "Version of showlist " + self.showlist.profile_version_string +
                " is not  same as Pi Presents")
            self.end(
                'error',
                "Version of showlist " + self.showlist.profile_version_string +
                " is not  same as Pi Presents")

        # get the 'start' show from the showlist
        index = self.showlist.index_of_start_show()
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', "Show [start] not found in showlist")

# ********************
# SET UP THE GUI
# ********************
# turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        # find connected displays and create a canvas for each display
        self.dm = DisplayManager()
        status, message, self.root = self.dm.init(self.options,
                                                  self.handle_user_abort)
        if status != 'normal':
            self.mon.err(self, message)
            self.end('error', message)

        self.mon.log(
            self,
            str(DisplayManager.num_displays) + ' Displays are connected:')

        for display_name in DisplayManager.display_map:
            status, message, display_id, canvas_obj = self.dm.id_of_canvas(
                display_name)
            if status != 'normal':
                continue
            width, height = self.dm.real_display_dimensions(display_id)
            self.mon.log(
                self, '   - ' + self.dm.name_of_display(display_id) + ' Id: ' +
                str(display_id) + ' ' + str(width) + '*' + str(height))
            canvas_obj.config(bg=self.starter_show['background-colour'])

# ****************************************
# INITIALISE THE TOUCHSCREEN DRIVER
# ****************************************

# each driver takes a set of inputs, binds them to symboic names
# and sets up a callback which returns the symbolic name when an input event occurs

        self.sr = ScreenDriver()
        # read the screen click area config file
        reason, message = self.sr.read(pp_dir, self.pp_home, self.pp_profile)
        if reason == 'error':
            self.end('error', 'cannot find, or error in screen.cfg')

        # create click areas on the canvases, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason, message = self.sr.make_click_areas(self.handle_input_event)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False
        self.reboot_required = False
        self.terminate_required = False
        self.exitpipresents_required = False

        # initialise the Beeps Manager
        self.beepsmanager = BeepsManager()
        self.beepsmanager.init(self.pp_home, self.pp_profile)

        # initialise the I/O plugins by importing their drivers
        self.ioplugin_manager = IOPluginManager()
        reason, message = self.ioplugin_manager.init(self.pp_dir,
                                                     self.pp_profile,
                                                     self.root,
                                                     self.handle_input_event,
                                                     self.pp_home)
        if reason == 'error':
            # self.mon.err(self,message)
            self.end('error', message)

        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir, self.pp_home, self.pp_profile, self.root,
                          200, self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id = -1
        self.show_manager = ShowManager(show_id, self.showlist,
                                        self.starter_show, self.root,
                                        self.pp_dir, self.pp_profile,
                                        self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback,
                               self.handle_command, self.showlist)
        # Register all the shows in the showlist
        reason, message = self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled = False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config' +
                              os.sep + 'osc.cfg'):
                self.oscdriver = OSCDriver()
                reason, message = self.oscdriver.init(
                    self.pp_profile, self.unit, self.interface, self.ip,
                    self.handle_command, self.handle_input_event,
                    self.e_osc_handle_animate)
                if reason == 'error':
                    self.mon.err(self, message)
                    self.end('error', message)
                else:
                    self.osc_enabled = True
                    self.root.after(1000, self.oscdriver.start_server())

        # initialise ToD scheduler calculating schedule for today
        self.tod = TimeOfDay()
        reason, message, self.tod_enabled = self.tod.init(
            pp_dir, self.pp_home, self.pp_profile, self.showlist, self.root,
            self.handle_command)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # warn if the network not available when ToD required
        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(
                self,
                'Network not connected  so Time of Day scheduler may be using the internal clock'
            )

        # init the counter manager
        self.counter_manager = CounterManager()
        if self.starter_show['counters-store'] == 'yes':
            store_enable = True
        else:
            store_enable = False
        reason, message = self.counter_manager.init(
            self.pp_profile + '/counters.cfg', store_enable,
            self.options['loadcounters'],
            self.starter_show['counters-initial'])
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

        # warn about start shows and scheduler

        if self.starter_show['start-show'] == '' and self.tod_enabled is False:
            self.mon.sched(
                self, None,
                "No Start Shows in Start Show and no shows scheduled")
            self.mon.warn(
                self, "No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] != '' and self.tod_enabled is True:
            self.mon.sched(
                self, None,
                "Start Shows in Start Show and shows scheduled - conflict?")
            self.mon.warn(
                self,
                "Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod.poll()

        # start the I/O plugins input event generation
        self.ioplugin_manager.start()

        # start Tkinters event loop
        self.root.mainloop()

# *********************
#  RUN START SHOWS
# ********************

    def run_start_shows(self):
        self.mon.trace(self, 'run start shows')
        # parse the start shows field and start the initial shows
        show_refs = self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason, message = self.show_manager.control_a_show(
                show_ref, 'open')
            if reason == 'error':
                self.mon.err(self, message)

# *********************
# User inputs
# ********************

    def e_osc_handle_animate(self, line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_animate(arg))

    def osc_handle_animate(self, line):
        self.mon.log(self, "animate command received: " + line)
        #osc sends output events as a string
        reason, message, delay, name, param_type, param_values = self.animate.parse_animate_fields(
            line)
        if reason == 'error':
            self.mon.err(self, message)
            self.end(reason, message)
        self.handle_output_event(name, param_type, param_values, 0)

    # output events are animate commands
    def handle_output_event(self, symbol, param_type, param_values, req_time):
        reason, message = self.ioplugin_manager.handle_output_event(
            symbol, param_type, param_values, req_time)
        if reason == 'error':
            self.mon.err(self, message)
            self.end(reason, message)

    # all input events call this callback providing a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self, symbol, source):
        self.mon.log(self, "event received: " + symbol + ' from ' + source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()

        elif symbol == 'pp-shutdown':
            self.mon.err(
                self,
                'pp-shutdown removed in version 1.3.3a, see Release Notes')
            self.end(
                'error',
                'pp-shutdown removed in version 1.3.3a, see Release Notes')

        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1, self.shutdownnow_pressed)
            return

        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            reason, message = self.show_manager.exit_all_shows()
        else:
            # pass the input event to all registered shows
            for show in self.show_manager.shows:
                show_obj = show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)

    # commands are generated by tracks and shows
    # they can open or close shows, generate input events and do special tasks
    # commands also generate osc outputs to other computers
    # handles one command provided as a line of text

    def handle_command(self, command_text, source='', show=''):
        # print 'PIPRESENTS ',command_text,'\n   Source',source,'from',show
        self.mon.log(self, "command received: " + command_text)
        if command_text.strip() == "":
            return

        fields = command_text.split()

        if fields[0] in ('osc', 'OSC'):
            if self.osc_enabled is True:
                status, message = self.oscdriver.parse_osc_command(fields[1:])
                if status == 'warn':
                    self.mon.warn(self, message)
                if status == 'error':
                    self.mon.err(self, message)
                    self.end('error', message)
                return
            else:
                return

        if fields[0] == 'counter':
            status, message = self.counter_manager.parse_counter_command(
                fields[1:])
            if status == 'error':
                self.mon.err(self, message)
                self.end('error', message)
            return

        if fields[0] == 'beep':
            # cheat, field 0 will always be beep
            message, fields = self.beepsmanager.parse_beep(command_text)
            if message != '':
                self.mon.err(self, message)
                self.end('error', message)
                return
            location = self.beepsmanager.complete_path(fields[1])
            if not os.path.exists(location):
                message = 'Beep file does not exist: ' + location
                self.mon.err(self, message)
                self.end('error', message)
                return
            else:
                self.beepsmanager.do_beep(location)
            return

        show_command = fields[0]
        if len(fields) > 1:
            show_ref = fields[1]
        else:
            show_ref = ''
        if show_command in ('open', 'close', 'closeall', 'openexclusive'):
            self.mon.sched(self, TimeOfDay.now,
                           command_text + ' received from show:' + show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason, message = self.show_manager.control_a_show(
                    show_ref, show_command)
            else:
                return

        elif show_command == 'monitor':
            self.handle_monitor_command(show_ref)
            return

        elif show_command == 'cec':
            self.handle_cec_command(show_ref)
            return

        elif show_command == 'event':
            self.handle_input_event(show_ref, 'Show Control')
            return

        elif show_command == 'exitpipresents':
            self.exitpipresents_required = True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1, self.e_all_shows_ended_callback)
                return
            else:
                reason, message = self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1, self.shutdownnow_pressed)
            return

        elif show_command == 'reboot':
            # need root.after to get out of st thread
            self.root.after(1, self.reboot_pressed)
            return

        else:
            reason = 'error'
            message = 'command not recognised: ' + show_command

        if reason == 'error':
            self.mon.err(self, message)
        return

    def handle_monitor_command(self, command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')

    def handle_cec_command(self, command):
        if command == 'on':
            os.system('echo "on 0" | cec-client -s')
        elif command == 'standby':
            os.system('echo "standby 0" | cec-client -s')

        elif command == 'scan':
            os.system('echo scan | cec-client -s -d 1')

    # deal with differnt commands/input events

    def shutdownnow_pressed(self):
        self.shutdown_required = True
        if self.show_manager.all_shows_exited() is True:
            self.all_shows_ended_callback('normal', 'no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def reboot_pressed(self):
        self.reboot_required = True
        if self.show_manager.all_shows_exited() is True:
            self.all_shows_ended_callback('normal', 'no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def handle_sigterm(self, signum, fframe):
        self.mon.log(self, 'SIGTERM received - ' + str(signum))
        self.terminate()

    def handle_user_abort(self):
        self.mon.log(self, 'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required = True
        needs_termination = False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination = True
                self.mon.log(
                    self,
                    "Sent terminate to show " + show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed', 'killed - no termination of shows required')

# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal', 'no shows running')

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self, reason, message):
        for display_name in DisplayManager.display_map:
            status, message, display_id, canvas_obj = self.dm.id_of_canvas(
                display_name)
            if status != 'normal':
                continue
            canvas_obj.config(bg=self.starter_show['background-colour'])
        if reason in (
                'killed', 'error'
        ) or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True:
            self.end(reason, message)

    def end(self, reason, message):
        self.mon.log(self, "Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip
                self.send_email(reason, subject, message)
            self.mon.sched(self, None, "Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")

            # close logging files
            self.mon.finish()
            print('Uncollectable Garbage', gc.collect())
            # objgraph.show_backrefs(objgraph.by_type('Canvas'),filename='backrefs.png')
            sys.exit(101)

        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject = '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: ' + message + '\n' + time.strftime(
                    "%Y-%m-%d %H:%M"
                ) + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip
                self.send_email(reason, subject, message_text)
            self.mon.sched(self, None,
                           "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")

            # close logging files
            self.mon.finish()
            print('uncollectable garbage', gc.collect())
            sys.exit(102)

        else:
            self.mon.sched(self, None, "Pi Presents  exiting normally, bye\n")
            self.mon.log(self, "Pi Presents  exiting normally, bye")

            # close logging files
            self.mon.finish()
            if self.reboot_required is True:
                # print 'REBOOT'
                call(['sudo', 'reboot'])
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call(['sudo', 'shutdown', 'now', 'SHUTTING DOWN'])
            print('uncollectable garbage', gc.collect())
            sys.exit(100)

    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset", "s", "on"])
            call(["xset", "s", "+dpms"])

        # tidy up animation
        if self.animate is not None:
            self.animate.terminate()

        # tidy up i/o plugins
        if self.ioplugin_manager != None:
            self.ioplugin_manager.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()

        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()


# *******************************
# Connecting to network and email
# *******************************

    def init_network(self):

        timeout = int(self.options['nonetwork'])
        if timeout == 0:
            self.network_connected = False
            self.unit = ''
            self.ip = ''
            self.interface = ''
            return

        self.network = Network()
        self.network_connected = False

        # try to connect to network
        self.mon.log(self,
                     'Waiting up to ' + str(timeout) + ' seconds for network')
        success = self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(
                self, 'Failed to connect to network after ' + str(timeout) +
                ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected = True
        self.mon.sched(
            self, None, 'Time after network check is ' +
            time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log(
            self, 'Time after network check is ' +
            time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details = False
        network_options_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(
                self, "pp_web.cfg not found at " + network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit = self.network.unit

        # get interface and IP details of preferred interface
        self.interface, self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected = False
            return
        self.network_details = True
        self.mon.log(
            self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +
            self.ip)

    def init_mailer(self):

        self.email_enabled = False
        email_file_path = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self, 'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self, 'Found pp_email.cfg at ' + email_file_path)
        self.mailer = Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled = True
            self.mon.log(self, 'Email Enabled')

    def try_connect(self):
        tries = 1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(
                    self, 'Failed to connect to email SMTP server ' +
                    str(tries) + '\n ' + str(error))
                tries += 1
                if tries > 5:
                    self.mon.log(
                        self, 'Failed to connect to email SMTP server after ' +
                        str(tries))
                    return False

    def send_email(self, reason, subject, message):
        if self.try_connect() is False:
            return False
        else:
            success, error = self.mailer.send(subject, message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success, error = self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,
                                 'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self, 'Sent email for ' + reason)
                success, error = self.mailer.disconnect()
                if success is False:
                    self.mon.log(
                        self,
                        'Failed disconnect from email server ' + str(error))
                return True
Example #22
0
class PiPresents:
    def __init__(self):

        self.pipresents_issue = "1.2"
        self.pipresents_minorissue = '1.2.3f'
        self.nonfull_window_width = 0.5  # proportion of width
        self.nonfull_window_height = 0.6  # proportion of height
        self.nonfull_window_x = 0  # position of top left corner
        self.nonfull_window_y = 0  # position of top left corner

        StopWatch.global_enable = False

        #****************************************
        # Initialisation
        # ***************************************
        # get command line options
        self.options = command_options()

        # get pi presents code directory
        pp_dir = sys.path[0]
        self.pp_dir = pp_dir

        if not os.path.exists(pp_dir + "/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents",
                                     "Bad Application Directory")
            exit()

        #Initialise logging
        Monitor.log_path = pp_dir
        self.mon = Monitor()
        self.mon.on()
        # 0  - errors only
        # 1  - errors and warnings
        # 2  - everything
        if self.options['debug'] == True:
            Monitor.global_enable = 2
        else:
            Monitor.global_enable = 0

        # UNCOMMENT THIS TO LOG WARNINGS AND ERRORS ONLY
        # Monitor.global_enable=1

        self.mon.log(
            self,
            "\n\n\n\n\n*****************\nPi Presents is starting, Version:" +
            self.pipresents_minorissue)
        self.mon.log(self, "Version: " + self.pipresents_minorissue)
        self.mon.log(self, " OS and separator:" + os.name + '  ' + os.sep)
        self.mon.log(self, "sys.path[0] -  location of code: " + sys.path[0])
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME'))
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))

        # optional other classes used
        self.ppio = None
        self.tod = None

        #get profile path from -p option
        if self.options['profile'] <> "":
            self.pp_profile_path = "/pp_profiles/" + self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"

    #get directory containing pp_home from the command,
        if self.options['home'] == "":
            home = os.path.expanduser('~') + os.sep + "pp_home"
        else:
            home = self.options['home'] + os.sep + "pp_home"
        self.mon.log(self, "pp_home directory is: " + home)

        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home = pp_dir + "/pp_home"
        found = False
        for i in range(1, 10):
            self.mon.log(self,
                         "Trying pp_home at: " + home + " (" + str(i) + ')')
            if os.path.exists(home):
                found = True
                self.pp_home = home
                break
            time.sleep(1)
        if found == True:
            self.mon.log(
                self,
                "Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.log(
                self,
                "FAILED to find requested home directory, using default to display error message: "
                + self.pp_home)

        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile = self.pp_home + self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.log(
                self, "Found Requested profile - pp_profile directory is: " +
                self.pp_profile)
        else:
            self.pp_profile = pp_dir + "/pp_home/pp_profiles/pp_profile"
            self.mon.log(
                self,
                "FAILED to find requested profile, using default to display error message: pp_profile"
            )

        if self.options['verify'] == True:
            val = Validator()
            if val.validate_profile(None, pp_dir, self.pp_home,
                                    self.pp_profile, self.pipresents_issue,
                                    False) == False:
                tkMessageBox.showwarning("Pi Presents", "Validation Failed")
                exit()

        # open the resources
        self.rr = ResourceReader()
        # read the file, done once for all the other classes to use.
        if self.rr.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find resources.cfg')

        #initialise and read the showlist in the profile
        self.showlist = ShowList()
        self.showlist_file = self.pp_profile + "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self, "showlist not found at " + self.showlist_file)
            self.end('error', 'showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) <> float(self.pipresents_issue):
            self.mon.err(
                self, "Version of profile " + self.showlist.sissue() +
                " is not  same as Pi Presents, must exit")
            self.end('error', 'wrong version of profile')

        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >= 0:
            self.showlist.select(index)
            self.starter_show = self.showlist.selected_show()
        else:
            self.mon.err(self, "Show [start] not found in showlist")
            self.end('error', 'start show not found')

# ********************
# SET UP THE GUI
# ********************
#turn off the screenblanking and saver
        if self.options['noblank'] == True:
            call(["xset", "s", "off"])
            call(["xset", "s", "-dpms"])

        self.root = Tk()

        self.title = 'Pi Presents - ' + self.pp_profile
        self.icon_text = 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')

        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions and decorations
        if self.options['fullscreen'] == True:

            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_width = self.screen_width
            self.window_height = self.screen_height
            self.window_x = 0
            self.window_y = 0
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))
            self.root.attributes('-zoomed', '1')
        else:
            self.window_width = int(self.screen_width *
                                    self.nonfull_window_width)
            self.window_height = int(self.screen_height *
                                     self.nonfull_window_height)
            self.window_x = self.nonfull_window_x
            self.window_y = self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" %
                               (self.window_width, self.window_height,
                                self.window_x, self.window_y))

        #canvas covers the whole window
        self.canvas_height = self.screen_height
        self.canvas_width = self.screen_width

        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol("WM_DELETE_WINDOW", self.exit_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height,
                           width=self.canvas_width,
                           highlightthickness=0)
        # self.canvas.pack()
        self.canvas.place(x=0, y=0)

        self.canvas.focus_set()

        # ****************************************
        # INITIALISE THE INPUT DRIVERS
        # ****************************************

        # looks after bindings between symbolic names and internal operations
        controlsmanager = ControlsManager()
        if controlsmanager.read(pp_dir, self.pp_home,
                                self.pp_profile) == False:
            self.end('error', 'cannot find or error in controls.cfg.cfg')
        else:
            controlsmanager.parse_defaults()

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd = KbdDriver()
        if kbd.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find or error in keys.cfg')
        kbd.bind_keys(self.root, self.input_pressed)

        self.sr = ScreenDriver()
        # read the screen click area config file
        if self.sr.read(pp_dir, self.pp_home, self.pp_profile) == False:
            self.end('error', 'cannot find screen.cfg')

        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        reason, message = self.sr.make_click_areas(self.canvas,
                                                   self.input_pressed)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required = False

        #kick off GPIO if enabled by command line option
        if self.options['gpio'] == True:
            from pp_gpio import PPIO
            # initialise the GPIO
            self.ppio = PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir, self.pp_home, self.pp_profile,
                              self.canvas, 50, self.gpio_pressed) == False:
                self.end('error', 'gpio error')

            # and start polling gpio
            self.ppio.poll()

        #kick off the time of day scheduler
        self.tod = TimeOfDay()
        self.tod.init(pp_dir, self.pp_home, self.canvas, 500)
        self.tod.poll()

        # Create list of start shows initialise them and then run them
        self.run_start_shows()

        #start tkinter
        self.root.mainloop()

# *********************
#  RUN START SHOWS
# ********************

    def run_start_shows(self):
        #start show manager
        show_id = -1  #start show
        self.show_manager = ShowManager(show_id, self.showlist,
                                        self.starter_show, self.root,
                                        self.canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)

        #first time through so empty show register and set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.all_shows_ended_callback)

        #parse the start shows field and start the initial shows
        start_shows_text = self.starter_show['start-show']
        self.show_manager.start_initial_shows(start_shows_text)

    #callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self, reason, message, force_shutdown):
        self.mon.log(self, "All shows ended, so terminate Pi Presents")
        if force_shutdown == True:
            self.shutdown_required = True
            self.mon.log(self, "shutdown forced by profile")
            self.terminate('killed')
        else:
            self.end(reason, message)

# *********************
# User inputs
# ********************

#gpio callback - symbol provided by gpio

    def gpio_pressed(self, index, symbol, edge):
        self.mon.log(self, "GPIO Pressed: " + symbol)
        self.input_pressed(symbol, edge, 'gpio')

    # all input events call this callback with a symbolic name.
    def input_pressed(self, symbol, edge, source):
        self.mon.log(self, "input received: " + symbol)
        if symbol == 'pp-exit':
            self.exit_pressed()
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
        elif symbol == 'pp-shutdownnow':
            self.shutdown_pressed('now')
        else:
            for show in self.show_manager.shows:
                show_obj = show[ShowManager.SHOW_OBJ]
                if show_obj <> None:
                    show_obj.input_pressed(symbol, edge, source)

# **************************************
# respond to exit inputs by terminating
# **************************************

    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000, self.on_shutdown_delay)
        else:
            self.shutdown_required = True
            self.exit_pressed()

    def on_shutdown_delay(self):
        if self.ppio.shutdown_pressed():
            self.shutdown_required = True
            self.exit_pressed()

    def exit_pressed(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players
        self.mon.log(self, "kill sent to shows")
        self.terminate('killed')

    # kill or error
    def terminate(self, reason):
        needs_termination = False
        for show in self.show_manager.shows:
            if show[ShowManager.SHOW_OBJ] <> None:
                needs_termination = True
                self.mon.log(
                    self,
                    "Sent terminate to show " + show[ShowManager.SHOW_REF])
                show[ShowManager.SHOW_OBJ].terminate(reason)
        if needs_termination == False:
            self.end(reason,
                     'terminate - no termination of lower levels required')

# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def end(self, reason, message):
        self.mon.log(
            self, "Pi Presents ending with message: " + reason + ' ' + message)
        if reason == 'error':
            self.tidy_up()
            self.mon.log(self, "exiting because of error")
            #close logging files
            self.mon.finish()
            exit()
        else:
            self.tidy_up()
            self.mon.log(self, "no error - exiting normally")
            #close logging files
            self.mon.finish()
            if self.shutdown_required == True:
                # call(['sudo', 'shutdown', '-h', '-t 5','now'])
                call(['sudo', 'shutdown', '-h', 'now'])
                exit()
            else:
                exit()

    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank'] == True:
            call(["xset", "s", "on"])
            call(["xset", "s", "+dpms"])

        # tidy up gpio
        if self.options['gpio'] == True and self.ppio <> None:
            self.ppio.terminate()

        #tidy up time of day scheduler
        if self.tod <> None:
            self.tod.terminate()


# *****************************
# utilitities
# ****************************

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self.terminate("error")
        else:
            return value
Example #23
0
class OSCDriver(object):

    # executed by main program
    def  init(self,pp_profile,show_command_callback,input_event_callback,output_event_callback):

        self.pp_profile=pp_profile
        self.show_command_callback=show_command_callback
        self.input_event_callback=input_event_callback
        self.output_event_callback=output_event_callback

        self.mon=Monitor()
        config_file=self.pp_profile + os.sep +'pp_io_config'+os.sep+ 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self, 'OSC Configuration file nof found: '+config_file)
            return'error','OSC Configuration file nof found: '+config_file
        
        self.mon.log(self, 'OSC Configuration file found at: '+config_file)        
        self.options=OSCConfig()
        # only reads the  data for required unit_type 
        if self.options.read(config_file) ==False:
            return 'error','failed to read osc.cfg'

        self.prefix='/pipresents'
        self.this_unit='/' + self.options.this_unit_name
        self.this_unit_type = self.options.this_unit_type

        self.reply_client=None
        self.command_client=None
        self.client=None
        self.server=None

        if self.this_unit_type not in ('master','slave','master+slave'):
            return 'error','this unit type not known: '+self.this_unit_type

        if self.this_unit_type in('slave','master+slave'):
            #start the client that sends replies to controlling unit
            self.reply_client=OSC.OSCClient()
            self.mon.log(self, 'sending replies to controller at: '+self.options.controlled_by_ip+':'+self.options.controlled_by_port)
            self.reply_client.connect((self.options.controlled_by_ip,int(self.options.controlled_by_port)))
            self.mon.log(self,'sending repiles to: '+ str(self.reply_client))
            self.client=self.reply_client
            
        if self.this_unit_type in ('master','master+slave'):
            #start the client that sends commands to the controlled unit
            self.command_client=OSC.OSCClient()
            self.command_client.connect((self.options.controlled_unit_1_ip,int(self.options.controlled_unit_1_port)))
            self.mon.log(self, 'sending commands to controled unit at: '+self.options.controlled_unit_1_ip+':'+self.options.controlled_unit_1_port)
            self.mon.log(self,'sending commands to: '+str(self.command_client))
            self.client=self.command_client

        #start the listener's server
        self.mon.log(self, 'listen to commands from controlled by unit and replies from controlled units on: ' + self.options.this_unit_ip+':'+self.options.this_unit_port)
        self.server=myOSCServer((self.options.this_unit_ip,int(self.options.this_unit_port)),self.client)
        # return_port=int(self.options.controlled_by_port)
        self.mon.log(self,'listening on: '+str(self.server))
        self.add_initial_handlers()
        return 'normal','osc.cfg read'

    def terminate(self):
        if self.server != None:
            self.server.close()
        self.mon.log(self, 'Waiting for Server-thread to finish')
        if self.st != None:
            self.st.join() ##!!!
        self.mon.log(self,'server thread closed')
        self.client.close()



    def start_server(self):
        # Start OSCServer
        self.mon.log(self,'Starting OSCServer')
        self.st = threading.Thread( target = self.server.serve_forever )
        self.st.start()


    def add_initial_handlers(self):
        self.server.addMsgHandler('default', self.no_match_handler)        
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/server-info", self.server_info_handler)
        self.server.addMsgHandler(self.prefix+self.this_unit+"/system/loopback", self.loopback_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/open', self.open_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/close', self.close_show_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/exitpipresents', self.exitpipresents_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/shutdownnow', self.shutdownnow_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/event', self.input_event_handler)
        self.server.addMsgHandler(self.prefix+ self.this_unit+'/core/output', self.output_event_handler)


    def no_match_handler(self,addr, tags, stuff, source):
        self.mon.warn(self,"no match for osc msg with addr : %s" % addr)
        return None


    def server_info_handler(self,addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/server-info-reply')
        msg.append('Unit: '+ self.options.this_unit_name)
        return msg


    def loopback_handler(self,addr, tags, stuff, source):
         # send a reply to the client.
        msg = OSC.OSCMessage(self.prefix+'/'+self.options.controlled_by_name+'/system/loopback-reply')
        return msg

    
    def open_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('open ',args,1)
        
    def close_show_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('close ', args,1)

    def exitpipresents_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents',args,0)

    def shutdownnow_handler(self,address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow',args,0)

    def prepare_show_command_callback(self,command,args,limit):
        if len(args) == limit:
            if limit !=0:
                self.show_command_callback(command+args[0])
            else:
                self.show_command_callback(command)                
        else:
            self.mon.warn(self,'OSC show command does not have '+limit +' argument - ignoring')  

    def input_event_handler(self,address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0],'OSC')
        else:
            self.mon.warn(self,'OSC input event does not have 1 argument - ignoring')    


    def output_event_handler(self,address, tags, args, source):
        if len(args) !=0:
            # delay symbol,param_type,param_values,req_time as a string
            text='0 '
            for arg in args:
                text= text+ arg + ' '
            text = text + '0'
            self.output_event_callback(text)
        else:
            self.mon.warn(self,'OSC output event has no arguments - ignoring')      


    #send messages to controlled units
    # parses the message string into fields and sends - NO error checking
    def send_command(self,text):
        self.mon.log(self,'send OSC Command: ' + text )
        if self.this_unit_type not in ('master','remote','master+slave'):
            self.mon.warn(self,'Unit is not an OSC Master, ignoring command')
            return
        fields=text.split()
        address = fields[0]
        # print 'ADDRESS'+address
        address_fields=address[1:].split('/')
        if address_fields[0] != 'pipresents':
            self.mon.warn(self,'prefix is not pipresents: '+address_fields[0])
        if address_fields[1] != self.options.controlled_unit_1_name:
             self.mon.warn(self,'not sending OSC to the controlled unit: ' +self.options.controlled_unit_1_name + ' is '+ address_fields[1])
        arg_list=fields[1:]
        self.send(address,arg_list)


    def send(self,address,arg_list):
        # print self.command_client
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(address)
        for arg in arg_list:
            # print arg
            msg.append(arg)
        self.command_client.send(msg)    
class PiPresents(object):

    def __init__(self):
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3"
        self.pipresents_minorissue = '1.3.1g'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner
        self.pp_background='black'

        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory:\n{0}".format(pp_dir))
            exit(103)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()
        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents', 'pp_paths',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'PathManager','ControlsManager','ShowManager','PluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'KbdDriver','GPIODriver','TimeOfDay','ScreenDriver','Animate','OSCDriver'
                            ]

        # Monitor.classes=['PiPresents','ArtMediaShow','VideoPlayer','OMXDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)    
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue)
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        if os.geteuid() !=0:
            user=os.getenv('USER')
        else:
            user = os.getenv('SUDO_USER')
        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work

        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.gpiodriver=None
        self.oscdriver=None
        self.osc_enabled=False
        self.gpio_enabled=False
        self.tod_enabled=False
         
        # get home path from -o option
        self.pp_home = pp_paths.get_home(self.options['home'])
        if self.pp_home is None:
            self.end('error','Failed to find pp_home')

        # get profile path from -p option
        # pp_profile is the full path to the directory that contains 
        # pp_showlist.json and other files for the profile
        self.pp_profile = pp_paths.get_profile_dir(self.pp_home, self.options['profile'])
        if self.pp_profile is None:
            self.end('error','Failed to find profile')

        # check profile exists
        if os.path.exists(self.pp_profile):
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error','Failed to find profile')

        self.mon.start_stats(self.options['profile'])
        
        # check 'verify' option
        if self.options['verify'] is True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) is  False:
                self.mon.err(self,"Validation Failed")
                self.end('error','Validation Failed')

        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error','showlist not found')

        # check profile and Pi Presents issues are compatible
        if float(self.showlist.sissue()) != float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self.end('error','wrong version of profile')


        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error','start show not found')

        if self.starter_show['start-show']=='':
             self.mon.warn(self,"No Start Shows in Start Show")       

# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.pp_background)

        self.mon.log(self, 'native screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixcels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'commanded screen dimensions are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixcels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter 1>&- 2>&- &') # Suppress 'someone created a subwindow' complaints from unclutter
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvs cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.pp_background)


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE INPUT DRIVERS
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs/

        # use keyboard driver to bind keys to symbolic names and to set up callback
        kbd=KbdDriver()
        if kbd.read(pp_dir,self.pp_home,self.pp_profile) is False:
            self.end('error','cannot find or error in keys.cfg')
        kbd.bind_keys(self.root,self.handle_input_event)

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.exitpipresents_required=False
        
        # kick off GPIO if enabled by command line option
        self.gpio_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+os.sep+ 'gpio.cfg'):
            # initialise the GPIO
            self.gpiodriver=GPIODriver()
            reason,message=self.gpiodriver.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,50,self.handle_input_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.gpio_enabled=True
                # and start polling gpio
                self.gpiodriver.poll()
            
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)

        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
            self.oscdriver=OSCDriver()
            reason,message=self.oscdriver.init(self.pp_profile,self.handle_command,self.handle_input_event,self.e_osc_handle_output_event)
            if reason == 'error':
                self.end('error',message)
            else:
                self.osc_enabled=True
                self.root.after(1000,self.oscdriver.start_server())

        # and run the start shows
        self.run_start_shows()

       # set up the time of day scheduler including catchup         
        self.tod_enabled=False
        if os.path.exists(self.pp_profile + os.sep + 'schedule.json'):
            # kick off the time of day scheduler which may run additional shows
            self.tod=TimeOfDay()
            self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.root,self.handle_command)
            self.tod_enabled = True


        # then start the time of day scheduler
        if self.tod_enabled is True:
            self.tod.poll()

        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --fullscreen comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in ---fullscreen',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************
    # handles one command provided as a line of text
    def handle_command(self,command_text):
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        if command_text[0]=='/': 
            if self.osc_enabled is True:
                self.oscdriver.send_command(command_text)
            return
        
        fields= command_text.split()
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''
            
        if show_command in ('open','close'):
            if self.shutdown_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return

    def e_all_shows_ended_callback(self):
            self.all_shows_ended_callback('normal','no shows running')

    def e_shutdown_pressed(self):
            self.shutdown_pressed('now')


    def e_osc_handle_output_event(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_output_event(arg))

    def  osc_handle_output_event(self,line):
        self.mon.log(self,"output event received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

               
    def handle_output_event(self,symbol,param_type,param_values,req_time):
        if self.gpio_enabled is True:
            reason,message=self.gpiodriver.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)
        else:
            self.mon.warn(self,'GPIO not enabled')


    # all input events call this callback with a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.shutdown_pressed('delay')
            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.e_shutdown_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # events for shows affect the show and could cause it to exit.
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    def shutdown_pressed(self, when):
        if when == 'delay':
            self.root.after(5000,self.on_shutdown_delay)
        else:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()           


    def on_shutdown_delay(self):
        # 5 second delay is up, if shutdown button still pressed then shutdown
        if self.gpiodriver.shutdown_pressed() is True:
            self.shutdown_required=True
            if self.show_manager.all_shows_exited() is True:
               self.all_shows_ended_callback('normal','no shows running')
            else:
                # calls exit method of all shows, results in all_shows_closed_callback
                self.show_manager.exit_all_shows()


    def handle_sigterm(self,signum,frame):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')


# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.pp_background)
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        # gc.collect()
        # print gc.garbage
        if reason == 'killed':
            self.mon.log(self, "Pi Presents Aborted, au revoir")
            # close logging files 
            self.mon.finish()
            sys.exit(101)     
        elif reason == 'error':
            self.mon.log(self, "Pi Presents closing because of error, sorry")
            # close logging files 
            self.mon.finish()
            sys.exit(102)            
        else:
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            # close logging files 
            self.mon.finish()
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call(['sudo', 'shutdown', '-h', '-t 5','now'])
                sys.exit(100)
            else:
                sys.exit(100)


    
    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation and gpio
        if self.animate is not None:
            self.animate.terminate()
            
        if self.gpio_enabled==True:
            self.gpiodriver.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()
Example #25
0
class Player(object):

    # common bits of __init__(...)
    def __init__(
        self,
        show_id,
        showlist,
        root,
        canvas,
        show_params,
        track_params,
        pp_dir,
        pp_home,
        pp_profile,
        end_callback,
        command_callback,
    ):

        # create debugging log object
        self.mon = Monitor()

        self.mon.trace(self, "")

        # instantiate arguments
        self.show_id = show_id
        self.showlist = showlist
        self.root = root
        self.canvas = canvas["canvas-obj"]
        self.show_canvas_x1 = canvas["show-canvas-x1"]
        self.show_canvas_y1 = canvas["show-canvas-y1"]
        self.show_canvas_x2 = canvas["show-canvas-x2"]
        self.show_canvas_y2 = canvas["show-canvas-y2"]
        self.show_canvas_width = canvas["show-canvas-width"]
        self.show_canvas_height = canvas["show-canvas-height"]
        self.show_canvas_centre_x = canvas["show-canvas-centre-x"]
        self.show_canvas_centre_y = canvas["show-canvas-centre-y"]
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.end_callback = end_callback
        self.command_callback = command_callback

        # get background image from profile.
        self.background_file = ""
        if self.track_params["background-image"] != "":
            self.background_file = self.track_params["background-image"]

        # get background colour from profile.
        if self.track_params["background-colour"] != "":
            self.background_colour = self.track_params["background-colour"]
        else:
            self.background_colour = self.show_params["background-colour"]

        # get animation instructions from profile
        self.animate_begin_text = self.track_params["animate-begin"]
        self.animate_end_text = self.track_params["animate-end"]

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim = PluginManager(
            self.show_id,
            self.root,
            self.canvas,
            self.show_params,
            self.track_params,
            self.pp_dir,
            self.pp_home,
            self.pp_profile,
        )

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj = None
        self.show_text_obj = None
        self.track_text_obj = None
        self.hint_obj = None
        self.background = None
        self.freeze_at_end_required = "no"  # overriden by videoplayer
        self.tick_timer = None
        self.terminate_signal = False
        self.play_state = ""

    def pre_load(self):
        # Control other shows at beginning
        self.show_control(self.track_params["show-control-begin"])
        pass

    # common bits of show(....)
    def pre_show(self):
        self.mon.trace(self, "")

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()

        # and whatecer the plugin has created
        self.pim.show_plugin()

        # ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)

        # create animation events
        reason, message = self.animate.animate(self.animate_begin_text, id(self))
        if reason == "error":
            self.mon.err(self, message)
            self.play_state = "show-failed"
            if self.finished_callback is not None:
                self.finished_callback("error", message)
        else:
            # return to start playing the track.
            self.mon.log(self, ">show track received from show Id: " + str(self.show_id))
            return

    # to keep landscape happy
    def ready_callback(self, enable_show_background):
        self.mon.fatal(self, "ready callback not overridden")
        self.end("error", "ready callback not overridden")

    def finished_callback(self, reason, message):
        self.mon.fatal(self, "finished callback not overridden")
        self.end("error", "finished callback not overridden")

    def closed_callback(self, reason, message):
        self.mon.fatal(self, "closed callback not overridden")
        self.end("error", "closed callback not overridden")

    # Control shows so pass the show control commands back to PiPresents via the command callback
    def show_control(self, show_control_text):
        lines = show_control_text.split("\n")
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line, self.show_params["show-ref"])

    # *****************
    # hide content and end animation, show control etc.
    # called by ready calback and end
    # *****************

    def hide(self):
        self.mon.trace(self, "")
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        self.hide_x_content()

        # stop the plugin
        if self.track_params["plugin"] != "":
            self.pim.stop_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params["show-control-end"])

        # clear events list for this track
        if self.track_params["animate-clear"] == "yes":
            self.animate.clear_events_list(id(self))

        # create animation events for ending
        reason, message = self.animate.animate(self.animate_end_text, id(self))
        if reason == "error":
            self.play_state = "show-failed"
            if self.finished_callback is not None:
                self.finished_callback("error", message)
        else:
            return

    def terminate(self):
        self.mon.trace(self, "")
        self.terminate_signal = True
        if self.play_state == "showing":
            # call the derived class's stop method
            self.stop()
        else:
            self.end("killed", "terminate with no track or show open")

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self, "stop not overidden by derived class")
        self.play_state = "show-failed"
        if self.finished_callback is not None:
            self.finished_callback("error", "stop not overidden by derived class")

    def get_play_state(self):
        return self.play_state

    # *****************
    # ending the player
    # *****************

    def end(self, reason, message):
        self.mon.trace(self, "")
        # stop the plugin

        if self.terminate_signal is True:
            reason = "killed"
            self.terminate_signal = False
            self.hide()

        self.end_callback(reason, message)
        self = None

    # *****************
    # displaying common things
    # *****************

    def load_plugin(self):
        # load the plugin if required
        if self.track_params["plugin"] != "":
            reason, message, self.track = self.pim.load_plugin(self.track, self.track_params["plugin"])
            return reason, message

    def draw_plugin(self):
        # load the plugin if required
        if self.track_params["plugin"] != "":
            self.pim.draw_plugin()
            return

    def load_x_content(self, enable_menu):
        self.mon.trace(self, "")
        self.background_obj = None
        self.background = None
        self.track_text_obj = None
        self.show_text_obj = None
        self.hint_obj = None
        self.track_obj = None

        # background image
        if self.background_file != "":
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return "error", "Track background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize((window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1, self.show_canvas_y1, image=self.background, anchor=NW
                )
                # print '\nloaded background_obj: ',self.background_obj

        # load the track content.  Dummy function below is overridden in players
        status, message = self.load_track_content()
        if status == "error":
            return "error", message

        # load show text if enabled
        if self.show_params["show-text"] != "" and self.track_params["display-show-text"] == "yes":
            self.show_text_obj = self.canvas.create_text(
                int(self.show_params["show-text-x"]) + self.show_canvas_x1,
                int(self.show_params["show-text-y"]) + self.show_canvas_y1,
                anchor=NW,
                text=self.show_params["show-text"],
                fill=self.show_params["show-text-colour"],
                font=self.show_params["show-text-font"],
            )

        # load track text if enabled
        if self.track_params["track-text"] != "":
            self.track_text_obj = self.canvas.create_text(
                int(self.track_params["track-text-x"]) + self.show_canvas_x1,
                int(self.track_params["track-text-y"]) + self.show_canvas_y1,
                anchor=NW,
                text=self.track_params["track-text"],
                fill=self.track_params["track-text-colour"],
                font=self.track_params["track-text-font"],
            )

        # load instructions if enabled
        if enable_menu is True:
            self.hint_obj = self.canvas.create_text(
                int(self.show_params["hint-x"]) + self.show_canvas_x1,
                int(self.show_params["hint-y"]) + self.show_canvas_y1,
                text=self.show_params["hint-text"],
                fill=self.show_params["hint-colour"],
                font=self.show_params["hint-font"],
                anchor=NW,
            )

        self.display_show_canvas_rectangle()

        self.pim.draw_plugin()

        self.canvas.tag_raise("pp-click-area")
        self.canvas.itemconfig(self.background_obj, state="hidden")
        self.canvas.itemconfig(self.show_text_obj, state="hidden")
        self.canvas.itemconfig(self.track_text_obj, state="hidden")
        self.canvas.itemconfig(self.hint_obj, state="hidden")
        self.canvas.update_idletasks()
        return "normal", "x-content loaded"

    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass

    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer

    def load_track_content(self):
        return "normal", "player has no track content to load"

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self, "")
        # background colour
        if self.background_colour != "":
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj, state="normal")
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj, state="normal")
        self.canvas.itemconfig(self.track_text_obj, state="normal")
        self.canvas.itemconfig(self.hint_obj, state="normal")
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params["display-show-background"] == "yes":
            self.enable_show_background = True
        else:
            self.enable_show_background = False
        # print 'ENABLE SB',self.enable_show_background

    def hide_x_content(self):
        self.mon.trace(self, "")
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj, state="hidden")
        self.canvas.itemconfig(self.show_text_obj, state="hidden")
        self.canvas.itemconfig(self.track_text_obj, state="hidden")
        self.canvas.itemconfig(self.hint_obj, state="hidden")
        # self.canvas.update_idletasks( )

        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background = None
        # self.canvas.update_idletasks( )

    # ****************
    # utilities
    # *****************

    def get_links(self):
        return self.track_params["links"]

    # produce an absolute path from the relative one in track paramters
    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        # self.mon.log(self,"Background image is "+ track_file)
        return track_file

    # get a text string from resources.cfg
    def resource(self, section, item):
        value = self.rr.get(section, item)
        return value  # False if not found
Example #26
0
class PiPresents:

    # Constants for list of start shows
    SHOW_TEMPLATE=['',None,-1]
    NAME = 0   # text name of the show
    SHOW = 1   # the show object
    ID = 2    # Numeic identity of the show object, sent to the instance and returned in callbacks
    
    def __init__(self):
        
        self.pipresents_issue="1.2"
        
        StopWatch.global_enable=False

#****************************************
# INTERPRET COMMAND LINE
# ***************************************

        self.options=command_options()
        

        pp_dir=sys.path[0]
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()

        
        #Initialise logging
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        self.mon.on()
        if self.options['debug']==True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False
 
        self.mon.log (self, "Pi Presents is starting")
        self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME'))
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))
        
        self.ppio=None
        self.tod=None
 
        # create  profile  for pp_editor test files if already not there.
        if not os.path.exists(pp_dir+"/pp_home/pp_profiles/pp_editor"):
            self.mon.log(self,"Making pp_editor directory") 
            os.makedirs(pp_dir+"/pp_home/pp_profiles/pp_editor")
            
            
        #profile path from -p option
        if self.options['profile']<>"":
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"
        
       #get directory containing pp_home from the command,
        if self.options['home'] =="":
            home = os.path.expanduser('~')+ os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"
            
        self.mon.log(self,"pp_home directory is: " + home)          
        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home=pp_dir+"/pp_home"
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                self.mon.log(self,"Using pp_home at: " + home)
                self.pp_home=home
                break
            time.sleep (1)

        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile=self.pp_home+self.pp_profile_path
        if not os.path.exists(self.pp_profile):
            self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile"

        if self.options['verify']==True:
            val =Validator()
            if  val.validate_profile(None,pp_dir,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False:
                tkMessageBox.showwarning("Pi Presents","Validation Failed")
                exit()
                
        # open the resources
        self.rr=ResourceReader()
        # read the file, done once for all the other classes to use.
        if self.rr.read(pp_dir,self.pp_home)==False:
            #self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self._end('error','cannot find resources.cfg')            

        
        #initialise the showlists and read the showlists
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self._end('error','showlist not found')

        if float(self.showlist.sissue())<>float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self._end('error','wrong version of profile')
 
        # get the 'start' show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self._end('error','start show not found')

        
# ********************
# SET UP THE GUI
# ********************
        #turn off the screenblanking and saver
        if self.options['noblank']==True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()
        # control display of window decorations
        if self.options['fullscreen']==True:
            self.root.attributes('-fullscreen', True)
            #self.root = Tk(className="fspipresents")
            os.system('unclutter &')
        else:
            #self.root = Tk(className="pipresents")
            pass


        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')
        
        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions
        self.window_height=self.screen_height
        self.window_width=self.screen_width
        self.window_x=0
        self.window_y=0
        if self.options['fullscreen']==True:
            bar=self.options['fullscreen']
            # allow just 2 pixels for the hidden taskbar - not any more
            if bar in ('left','right'):
                self.window_width=self.screen_width
            else:
                self.window_height=self.screen_height
            if bar =="left":
                self.window_x=0
            if bar =="top":
                self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')
        else:
            self.window_width=self.screen_width-600
            self.window_height=self.screen_height-200
            self.window_x=50
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
            

        #canvas covers the whole window
        self.canvas_height=self.window_height
        self.canvas_width=self.window_width
        
        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.on_break_key)

        # Always use CTRL-Break key to close the program as a get out of jail
        self.root.bind("<Break>",self.e_on_break_key)
        
        #pass all other keys along to start shows and hence to 'players'
        self.root.bind("<Escape>", self._escape_pressed)
        self.root.bind("<Up>", self._up_pressed)
        self.root.bind("<Down>", self._down_pressed)
        self.root.bind("<Return>", self._return_pressed)
        self.root.bind("<space>", self._pause_pressed)
        self.root.bind("p", self._pause_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height, width=self.canvas_width)
        self.canvas.pack()
        # make sure focus is set on canvas.
        self.canvas.focus_set()


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        
        #kick off GPIO if enabled by command line option
        if self.options['gpio']==True:
            from pp_gpio import PPIO
            # initialise the GPIO
            self.ppio=PPIO()
            # PPIO.gpio_enabled=False
            if self.ppio.init(pp_dir,self.pp_profile,self.canvas,50,self.button_pressed)==False:
                self._end('error','gpio error')
                
            # and start polling gpio
            self.ppio.poll()

        #kick off the time of day scheduler
        self.tod=TimeOfDay()
        self.tod.init(pp_dir,self.pp_home,self.canvas,500)
        self.tod.poll()

        # Create list of start shows initialise them and then run them
        self.start_shows = self.create_start_show_list()
        self.init_shows()
        self.run_shows()
        self.root.mainloop( )




# *********************
# EXIT APP
# *********************

    # kill or error
    def terminate(self,reason):
        needs_termination=False
        for start_show in self.start_shows:
            if start_show[PiPresents.SHOW]<>None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ start_show[PiPresents.NAME])
                start_show[PiPresents.SHOW].terminate(reason)
        if needs_termination==False:
            self._end(reason)



    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank']==True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
        # tidy up gpio
        if self.options['gpio']==True and self.ppio<>None:
            self.ppio.terminate()
        #tidy up time of day scheduler
        if self.tod<>None:
            self.tod.terminate()
        #close logging files 
        self.mon.finish()

        
    def on_kill_callback(self):
        self.tidy_up()
        if self.shutdown_required==True:
            call(['sudo', 'shutdown', '-h', '-t 5','now'])
        else:
            exit()

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value

# *********************
# Key and button presses
# ********************

    def shutdown_pressed(self):
        self.root.after(5000,self.on_shutdown_delay)

    def on_shutdown_delay(self):
        if self.ppio.is_pressed('shutdown'):
            self.shutdown_required=True
            self.on_break_key()

    def button_pressed(self,index,button,edge):
        self.mon.log(self, "Button Pressed: "+button)
        if button=="shutdown":
            self.shutdown_pressed()
        else:
            for start_show in self.start_shows:
                print "sending to show" , start_show[PiPresents.NAME]
                start_show[PiPresents.SHOW].button_pressed(button,edge)                 

  
    # key presses - convert from events to call to _key_pressed
    def _escape_pressed(self,event): self._key_pressed("escape")              
    def _up_pressed(self,event): self._key_pressed("up")  
    def _down_pressed(self,event): self._key_pressed("down")  
    def _return_pressed(self,event): self._key_pressed("return")
    def _pause_pressed(self,event): self._key_pressed("p")
        

    def _key_pressed(self,key_name):
        # key pressses are sent only to the controlled show.
        self.mon.log(self, "Key Pressed: "+ key_name)
        for start_show in self.start_shows:
                start_show[PiPresents.SHOW].key_pressed(key_name)          


         
    def on_break_key(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players     
        self.mon.log(self,"kill sent to shows")   
        self.terminate('killed')
 
 
    def e_on_break_key(self,event):
        self.on_break_key()

# *********************
# Start show creation and running and return from
# ********************   
        
# Extract shows from start show
    def create_start_show_list(self):
        start_shows_text=self.starter_show['start-show']
        shows=[]
        index=0
        fields= start_shows_text.split()
        for field in fields:
            show = PiPresents.SHOW_TEMPLATE        
            show[PiPresents.NAME]=field
            show[PiPresents.ID]=index 
            shows.append(copy.deepcopy(show))
            index+=1          
        return shows

    def init_shows(self):
        # build  list of shows to run by instantiating their classes.
        for start_show in self.start_shows:
            index = self.showlist.index_of_show(start_show[PiPresents.NAME])
            if index >=0:
                self.showlist.select(index)
                show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ start_show[PiPresents.NAME])
                self._end('error','show not found in showlist')
                
            if show['type']=="mediashow":
                show_obj = MediaShow(show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj


                
            elif show['type']=="menu":
                show_obj = MenuShow(show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj


            elif show['type']=="liveshow":
                show_obj= LiveShow(show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                start_show[PiPresents.SHOW]=show_obj                   
            else:
                self.mon.err(self,"unknown mediashow type in start show - "+ show['type'])
                self._end('error','unknown mediashow type')


    # run each of the shows in the list
    def run_shows(self):
        for start_show in self.start_shows:
                show_obj = start_show[PiPresents.SHOW]
                show_obj.play(start_show[PiPresents.ID],self._end_play_show,top=True,command='nil')


    def _end_play_show(self,show_id,reason,message):     
        self.mon.log(self,"Show " + str(show_id) + " returned to Pipresents with reason: " + reason )
        self.start_shows[show_id][PiPresents.SHOW]=None
        # if all the shows have ended then end Pi Presents
        all_terminated=True
        for start_show in self.start_shows:
            if start_show[PiPresents.SHOW]<>None:
                all_terminated=False
        if all_terminated==True:
            self._end(reason,message)
     
    def _end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with message: " + message)
        if reason=='error':
            self.mon.log(self, "exiting because of error")
            self.tidy_up()
            exit()            
        if reason=='killed':
            self.mon.log(self,"kill received - exiting")
            self.on_kill_callback()
        else:
            # should never be here or fatal error
            self.mon.log(self, "exiting because invalid end reasosn")
            self.tidy_up()
            exit()
class AudioPlayer:

    _CLOSED = "mplayer_closed"    #probably will not exist
    _STARTING = "mplayer_starting"  #track is being prepared
    _PLAYING = "mplayer_playing"  #track is playing to the screen, may be paused
    _ENDING = "mplayer_ending"  #track is in the process of ending due to quit or end of track
    _WAITING = "wait for timeout" # track has finished but timeout still running

    #_LEFT = "-af channels=2:1:0:0:1:1,resample=48000:1 "
   # _RIGHT = "-af channels=2:1:0:1:1:0,resample=48000:1 "
    #_STEREO = "-af channels=2,resample=48000:1 "

    _LEFT = "channels=2:1:0:0:1:1"
    _RIGHT = "channels=2:1:0:1:1:0"
    _STEREO = "channels=2"

# ***************************************
# EXTERNAL COMMANDS
# ***************************************

    def __init__(self,
                        show_id,
                        canvas,
                         pp_home,
                        show_params,
                        track_params ):
        """       
            canvas - the canvas onto which the background image is to be drawn
            show_params - configuration of show playing the track
            track_params - config dictionary for this track overrides show_params
        """

        self.mon=Monitor()
        self.mon.on()

        
        #instantiate arguments
        self.show_id=show_id
        self.show_params=show_params       #configuration dictionary for the videoplayer
        self.canvas = canvas  #canvas onto which video should be played but isn't! Use as widget for alarm
        self.pp_home=pp_home
        self.track_params=track_params

        # get duration (secs ) from profile
        self.duration= int(self.track_params['duration'])
        self.duration_limit=20*self.duration


        # get background image from profile.
        self.background_file  = self.track_params['background-image']


        # get audio sink from profile.
        if  self.track_params['mplayer-audio']<>"":
            self.mplayer_audio= self.track_params['mplayer-audio']
        else:
            self.mplayer_audio= self.show_params['mplayer-audio']
            
        # get audio volume from profile.
        if  self.track_params['mplayer-volume']<>"":
            self.mplayer_volume= self.track_params['mplayer-volume'].strip()
        else:
            self.mplayer_volume= self.show_params['mplayer-volume'].strip()
        self.volume_option= 'volume=' + self.mplayer_volume


        #get speaker from profile
        if  self.track_params['audio-speaker']<>"":
            self.audio_speaker= self.track_params['audio-speaker']
        else:
            self.audio_speaker= self.show_params['audio-speaker']

        if self.audio_speaker=='left':
            self.speaker_option=AudioPlayer._LEFT
        elif self.audio_speaker=='right':
            self.speaker_option=AudioPlayer._RIGHT
        else:
            self.speaker_option=AudioPlayer._STEREO

        #get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']


        #create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()

        # could put instance generation in play, not sure which is better.
        self.mplayer=mplayerDriver(self.canvas)
        self._tick_timer=None
        self.error=False
        self.terminate_me=False
        self._init_play_state_machine()



    def play(self, track,
                     end_callback,
                     ready_callback,
                     enable_menu=False, 
                     starting_callback=None,
                     playing_callback=None,
                     ending_callback=None):

        """
        play - plays the specified track, the first call after __init__
        track - full  path of track to play
        end_callback - callback when track ends (reason,message)
             reason = killed - return from a terminate with reason = killed
                           error - return because player or lower level has generated and runtime error
                           normal - anything else
            message = ant tesxt, used for debugging 
        ready_callback - callback when the track is ready to play, use to stop eggtimer etc.
        enable_menu - True if the track is to have a child show
        starting/playing/ending callback - called repeatedly in each state for show to display status, time etc.

        """
                         
        #instantiate arguments
        self.track=track
        self.ready_callback=ready_callback   #callback when ready to play
        self.enable_menu=enable_menu
        self.end_callback=end_callback         # callback when finished
        self.starting_callback=starting_callback  #callback during starting state
        self.playing_callback=playing_callback    #callback during playing state
        self.ending_callback=ending_callback      # callback during ending state
        # enable_menu is not used by AudioPlayer


        # select the sound sink
        if self.mplayer_audio<>"":
            if self.mplayer_audio=='hdmi':
                os.system("amixer -q -c 0 cset numid=3 2")
            else:
                os.system("amixer -q -c 0 cset numid=3 1")

        # callback to the calling object to e.g remove egg timer.
        if self.ready_callback<>None:
            self.ready_callback()

 
        # display image and text
        self.display_image()

        # create animation events
        error_text=self.ppio.animate(self.animate_begin_text,id(self))
        if error_text<>'':
            self.mon.err(self,error_text)
            self.error=True
            self._end('error',error_text)
 
        # and start playing the track.
        if self.play_state == AudioPlayer._CLOSED:
            self.mon.log(self,">play track received")
            self._start_play_state_machine()
            return True
        else:
            self.mon.log(self,"!< play track rejected")
            return False


    def key_pressed(self,key_name):
        """
        respond to user or system key  presses
        """
        if key_name=='':
            return
        elif key_name in ('p',' '):
            self._pause()
            return
        elif key_name=='escape':
            self._stop()
            return



    def button_pressed(self,button,edge):
        """
        respond to user button  presses
        """
        if button =='pause':
            self._pause()
            return
        elif button=='stop':
            self._stop()
            return


    def terminate(self,reason):
        """
        terminate the  player in special circumstances
        normal user termination if by key_pressed 'escape'
        reason will be killed or error
        """
        # circumvents state machine to terminate lower level and then itself.
        self.terminate_me=True
        if self.mplayer<>None:
            self.mon.log(self,"sent terminate to mplayerdriver")
            self.mplayer.terminate(reason)
            self._end('killed',' end without waiting') # end without waiting
        else:
            self.mon.log(self,"terminate, mplayerdriver not running")
            self._end('killed','terminate, mplayerdriver not running')
            
                

        
# ***************************************
# INTERNAL FUNCTIONS
# ***************************************

    #toggle pause
    def _pause(self):
        if self.play_state in (AudioPlayer._PLAYING,AudioPlayer._ENDING) and self.track<>'':
            self.mplayer.pause()
            return True
        else:
            self.mon.log(self,"!<pause rejected")
            return False
        
    # other control when playing, not currently used
    def _control(self,char):
        if self.play_state==AudioPlayer._PLAYING and self.track<>'':
            self.mon.log(self,"> send control to mplayer: "+ char)
            self.mplayer.control(char)
            return True
        else:
            self.mon.log(self,"!<control rejected")
            return False

    # respond to normal stop
    def _stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,">stop received")
        self._stop_required_signal=True

    #respond to internal error by setting flags to cause state machine to stop player
    #use this rather than end if the driver and its spawned process might still be running
    def _error(self):
        self.error=True
        self._stop_required_signal=True


    # tidy up and end AudioPlayer.
    def _end(self,reason,message):
            # self.canvas.delete(ALL)
            # abort the timer
            if self._tick_timer<>None:
                self.canvas.after_cancel(self._tick_timer)
                self._tick_timer=None
            
            if self.error==True or reason=='error':
                self.end_callback("error",message)
                self=None
                
            elif self.terminate_me==True:
                self.end_callback("killed",message)
                self=None
            else:
               # clear events list for this track
                if self.track_params['animate-clear']=='yes':
                    self.ppio.clear_events_list(id(self))
                # create animation events for ending
                error_text=self.ppio.animate(self.animate_end_text,id(self))
                if error_text=='':
                    self.end_callback('normal',"track has terminated or quit")
                    self=None
                else:
                    self.mon.err(self,error_text)
                    self.end_callback("error",error_text)
                    self=None
                
      
# ***************************************
# # PLAYING STATE MACHINE
# ***************************************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the mplayer process is not running, mplayer process can be initiated
         - _starting - mplayer process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - mplayer is doing its termination, controls cannot be sent
    """

    def _init_play_state_machine(self):
        self._stop_required_signal=False
        self.play_state=AudioPlayer._CLOSED
 
    def _start_play_state_machine(self):
        #initialise all the state machine variables
        self.duration_count = 0
        self._stop_required_signal=False     # signal that user has pressed stop

        #play the track

        options = self.show_params['mplayer-other-options'] + '-af '+ self.speaker_option+','+self.volume_option + ' '
        if self.track<>'':
            self.mplayer.play(self.track,options)
            self.mon.log (self,'Playing track from show Id: '+ str(self.show_id))
            self.play_state=AudioPlayer._STARTING
        else:
            self.play_state=AudioPlayer._PLAYING
        # and start polling for state changes and count duration
        self._tick_timer=self.canvas.after(50, self._play_state_machine)
 

    def _play_state_machine(self):
        self.duration_count+=1
           
        if self.play_state == AudioPlayer._CLOSED:
            self.mon.log(self,"      State machine: " + self.play_state)
            return 
                
        elif self.play_state == AudioPlayer._STARTING:
            self.mon.log(self,"      State machine: " + self.play_state)
            
            # if mplayer is playing the track change to play state
            if self.mplayer.start_play_signal==True:
                self.mon.log(self,"            <start play signal received from mplayer")
                self.mplayer.start_play_signal=False
                self.play_state=AudioPlayer._PLAYING
                self.mon.log(self,"      State machine: mplayer_playing started")
            self._do_starting()
            self._tick_timer=self.canvas.after(50, self._play_state_machine)

        elif self.play_state == AudioPlayer._PLAYING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # service any queued stop signals
            if self._stop_required_signal==True or (self.duration_limit<>0 and self.duration_count>self.duration_limit):
                self.mon.log(self,"      Service stop required signa or timeout")
                # self._stop_required_signal=False
                if self.track<>'':
                    self._stop_mplayer()
                    self.play_state = AudioPlayer._ENDING
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self._end('normal','stop required signa or timeout')

            # mplayer reports it is terminating so change to ending state
            if self.track<>'' and self.mplayer.end_play_signal:                    
                self.mon.log(self,"            <end play signal received")
                self.mon.log(self,"            <end detected at: " + str(self.mplayer.audio_position))
                self.play_state = AudioPlayer._ENDING
            self._do_playing()
            self._tick_timer=self.canvas.after(50, self._play_state_machine)

        elif self.play_state == AudioPlayer._ENDING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            self._do_ending()
            # if spawned process has closed can change to closed state
            # self.mon.log (self,"      State machine : is mplayer process running? -  "  + str(self.mplayer.is_running()))
            if self.mplayer.is_running() ==False:
                self.mon.log(self,"            <mplayer process is dead")
                if self._stop_required_signal==True:
                    self._stop_required_signal=False
                    self.play_state = AudioPlayer._CLOSED
                    self._end('normal','mplayer dead')
                elif self.duration_limit<>0 and self.duration_count<self.duration_limit:
                    self.play_state= AudioPlayer._WAITING
                    self._tick_timer=self.canvas.after(50, self._play_state_machine)
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self._end('normal','mplayer dead')
            else:
                self._tick_timer=self.canvas.after(50, self._play_state_machine)
                
        elif self.play_state == AudioPlayer._WAITING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            if self._stop_required_signal==True or (self.duration_limit<>0 and self.duration_count>self.duration_limit):
                self.mon.log(self,"      Service stop required signal or timeout from wait")
                self._stop_required_signal=False
                self.play_state = AudioPlayer._CLOSED
                self._end('normal','mplayer dead')
            else:
                self._tick_timer=self.canvas.after(50, self._play_state_machine)
                    



    # allow calling object do things in each state by calling the appropriate callback
 
    def _do_playing(self):
        if self.track<>'':
            self.audio_position=self.mplayer.audio_position
        if self.playing_callback<>None:
                self.playing_callback() 

    def _do_starting(self):
        self.audio_position=0.0
        if self.starting_callback<>None:
                self.starting_callback() 

    def _do_ending(self):
        if self.ending_callback<>None:
                self.ending_callback() 

    def _stop_mplayer(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,"         >stop mplayer received from state machine")
        if self.play_state==AudioPlayer._PLAYING:
            self.mplayer.stop()
            return True
        else:
            self.mon.log(self,"!<stop rejected")
            return False

# *****************
# image and text
# *****************
            
    def display_image(self):

        if self.background_file<>'' or self.show_params['show-text']<> '' or self.track_params['track-text']<> '' or self.enable_menu== True or self.track_params['clear-screen']=='yes':
            self.canvas.config(bg='black')
            self.canvas.delete(ALL)
        if self.background_file<>'':
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(self,"Audio background file not found: "+ self.background_img_file)
                self._end('error',"Audio background file not found")
            else:
                pil_background_img=PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                              image=self.background,
                                              anchor=CENTER)
                
        # display hint text if enabled
       
        if self.enable_menu== True:
            self.canvas.create_text(int(self.canvas['width'])/2, int(self.canvas['height']) - int(self.show_params['hint-y']),
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                font=self.show_params['hint-font'])

            
        # display show text if enabled
        if self.show_params['show-text']<> '':
            self.canvas.create_text(int(self.show_params['show-text-x']),int(self.show_params['show-text-y']),
                                                    anchor=NW,
                                                  text=self.show_params['show-text'],
                                                  fill=self.show_params['show-text-colour'],
                                                  font=self.show_params['show-text-font'])
            
        # display track text if enabled
        if self.track_params['track-text']<> '':
            self.canvas.create_text(int(self.track_params['track-text-x']),int(self.track_params['track-text-y']),
                                                    anchor=NW,
                                                  text=self.track_params['track-text'],
                                                  fill=self.track_params['track-text-colour'],
                                                  font=self.track_params['track-text-font'])

        self.mon.log(self,"Displayed background and text ")
        
        self.canvas.update_idletasks( )

    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Background image is "+ track_file)
        return track_file     
Example #28
0
class GPIODriver(object):
    """
    GPIODriver provides GPIO facilties for Pi presents
     - configures and binds GPIO pins from data in gpio.cfg
     - reads and debounces inputs pins, provides callbacks on state changes which generate input events
    - changes the stae of output pins as required by calling programs
    """

    # constants for buttons

    # cofiguration from gpio.cfg
    PIN = 0  # pin on RPi board GPIO connector e.g. P1-11
    DIRECTION = 1  # IN/OUT/NONE (None is not used)
    NAME = 2  # symbolic name for output
    RISING_NAME = 3  # symbolic name for rising edge callback
    FALLING_NAME = 4  # symbolic name of falling edge callback
    ONE_NAME = 5  # symbolic name for one state callback
    ZERO_NAME = 6  # symbolic name for zero state callback
    REPEAT = 7  # repeat interval for state callbacks (mS)
    THRESHOLD = 8  # threshold of debounce count for state change to be considered
    PULL = 9  # pull up or down or none

    # dynamic data
    COUNT = 10  # variable - count of the number of times the input has been 0 (limited to threshold)
    PRESSED = 11  # variable - debounced state
    LAST = 12  # varible - last state - used to detect edge
    REPEAT_COUNT = 13

    TEMPLATE = [
        '',  # pin
        '',  # direction
        '',  # name
        '',
        '',
        '',
        '',  #input names
        0,  # repeat
        0,  # threshold
        '',  #pull
        0,
        False,
        False,
        0
    ]  #dynamics

    # for A and B
    #    PINLIST = ('P1-03','P1-05','P1-07','P1-08',
    #               'P1-10','P1-11','P1-12','P1-13','P1-15','P1-16','P1-18','P1-19',
    #               'P1-21','P1-22','P1-23','P1-24','P1-26')

    # for A+ and B+ seems to work for A and B
    PINLIST = ('P1-03', 'P1-05', 'P1-07', 'P1-08', 'P1-10', 'P1-11', 'P1-12',
               'P1-13', 'P1-15', 'P1-16', 'P1-18', 'P1-19', 'P1-21', 'P1-22',
               'P1-23', 'P1-24', 'P1-26', 'P1-29', 'P1-31', 'P1-32', 'P1-33',
               'P1-35', 'P1-36', 'P1-37', 'P1-38', 'P1-40')

    # CLASS VARIABLES  (GPIODriver.)
    shutdown_index = 0  #index of shutdown pin
    pins = []
    options = None
    gpio_enabled = False

    # executed by main program and by each object using gpio
    def __init__(self):
        self.mon = Monitor()

    # executed once from main program
    def init(self,
             pp_dir,
             pp_home,
             pp_profile,
             widget,
             button_tick,
             button_callback=None):

        # instantiate arguments
        self.widget = widget
        self.pp_dir = pp_dir
        self.pp_profile = pp_profile
        self.pp_home = pp_home
        self.button_tick = button_tick
        self.button_callback = button_callback

        GPIODriver.shutdown_index = 0

        # read gpio.cfg file.
        reason, message = self.read(self.pp_dir, self.pp_home, self.pp_profile)
        if reason == 'error':
            return 'error', message

        if os.geteuid() != 0:
            self.mon.err(
                self,
                'GPIO requires Pi Presents to be run as root\nhint: sudo pipresents.py .... '
            )
            return 'error', 'GPIO without sudo'

        import RPi.GPIO as GPIO
        self.GPIO = GPIO

        # construct the GPIO control list from the configuration
        for index, pin_def in enumerate(GPIODriver.PINLIST):
            pin = copy.deepcopy(GPIODriver.TEMPLATE)
            pin_bits = pin_def.split('-')
            pin_num = pin_bits[1:]
            pin[GPIODriver.PIN] = int(pin_num[0])
            if self.config.has_section(pin_def) is False:
                self.mon.warn(self, "no pin definition for " + pin_def)
                pin[GPIODriver.DIRECTION] = 'None'
            else:
                # unused pin
                if self.config.get(pin_def, 'direction') == 'none':
                    pin[GPIODriver.DIRECTION] = 'none'
                else:
                    pin[GPIODriver.DIRECTION] = self.config.get(
                        pin_def, 'direction')
                    if pin[GPIODriver.DIRECTION] == 'in':
                        # input pin
                        pin[GPIODriver.RISING_NAME] = self.config.get(
                            pin_def, 'rising-name')
                        pin[GPIODriver.FALLING_NAME] = self.config.get(
                            pin_def, 'falling-name')
                        pin[GPIODriver.ONE_NAME] = self.config.get(
                            pin_def, 'one-name')
                        pin[GPIODriver.ZERO_NAME] = self.config.get(
                            pin_def, 'zero-name')
                        if pin[GPIODriver.FALLING_NAME] == 'pp-shutdown':
                            GPIODriver.shutdown_index = index
                        if self.config.get(pin_def, 'repeat') != '':
                            pin[GPIODriver.REPEAT] = int(
                                self.config.get(pin_def, 'repeat'))
                        else:
                            pin[GPIODriver.REPEAT] = -1
                        pin[GPIODriver.THRESHOLD] = int(
                            self.config.get(pin_def, 'threshold'))
                        if self.config.get(pin_def, 'pull-up-down') == 'up':
                            pin[GPIODriver.PULL] = GPIO.PUD_UP
                        elif self.config.get(pin_def,
                                             'pull-up-down') == 'down':
                            pin[GPIODriver.PULL] = GPIO.PUD_DOWN
                        else:
                            pin[GPIODriver.PULL] = GPIO.PUD_OFF
                    else:
                        # output pin
                        pin[GPIODriver.NAME] = self.config.get(pin_def, 'name')

            # print pin
            GPIODriver.pins.append(copy.deepcopy(pin))

        # setup GPIO
        self.GPIO.setwarnings(True)
        self.GPIO.setmode(self.GPIO.BOARD)

        # set up the GPIO inputs and outputs
        for index, pin in enumerate(GPIODriver.pins):
            num = pin[GPIODriver.PIN]
            if pin[GPIODriver.DIRECTION] == 'in':
                self.GPIO.setup(num,
                                self.GPIO.IN,
                                pull_up_down=pin[GPIODriver.PULL])
            elif pin[GPIODriver.DIRECTION] == 'out':
                self.GPIO.setup(num, self.GPIO.OUT)
                self.GPIO.setup(num, False)
        self.reset_input_state()

        GPIODriver.gpio_enabled = True

        # init timer
        self.button_tick_timer = None
        return 'normal', 'GPIO initialised'

    # called by main program only
    def poll(self):
        # loop to look at the buttons
        self.do_buttons()
        self.button_tick_timer = self.widget.after(self.button_tick, self.poll)

    # called by main program only
    def terminate(self):
        if GPIODriver.gpio_enabled is True:
            if self.button_tick_timer is not None:
                self.widget.after_cancel(self.button_tick_timer)
            self.reset_outputs()
            self.GPIO.cleanup()

# ************************************************
# gpio input functions
# called by main program only
# ************************************************

    def reset_input_state(self):
        for pin in GPIODriver.pins:
            pin[GPIODriver.COUNT] = 0
            pin[GPIODriver.PRESSED] = False
            pin[GPIODriver.LAST] = False
            pin[GPIODriver.REPEAT_COUNT] = pin[GPIODriver.REPEAT]

    # index is of the pins array, provided by the callback ***** needs to be name
    def shutdown_pressed(self):
        if GPIODriver.shutdown_index != 0:
            return GPIODriver.pins[GPIODriver.shutdown_index][
                GPIODriver.PRESSED]
        else:
            return False

    def do_buttons(self):
        for index, pin in enumerate(GPIODriver.pins):
            if pin[GPIODriver.DIRECTION] == 'in':
                # debounce
                if self.GPIO.input(pin[GPIODriver.PIN]) == 0:
                    if pin[GPIODriver.COUNT] < pin[GPIODriver.THRESHOLD]:
                        pin[GPIODriver.COUNT] += 1
                        if pin[GPIODriver.COUNT] == pin[GPIODriver.THRESHOLD]:
                            pin[GPIODriver.PRESSED] = True
                else:  # input us 1
                    if pin[GPIODriver.COUNT] > 0:
                        pin[GPIODriver.COUNT] -= 1
                        if pin[GPIODriver.COUNT] == 0:
                            pin[GPIODriver.PRESSED] = False

                # detect edges
                # falling edge
                if pin[GPIODriver.PRESSED] is True and pin[
                        GPIODriver.LAST] is False:
                    pin[GPIODriver.LAST] = pin[GPIODriver.PRESSED]
                    pin[GPIODriver.REPEAT_COUNT] = pin[GPIODriver.REPEAT]
                    if pin[GPIODriver.
                           FALLING_NAME] != '' and self.button_callback is not None:
                        self.button_callback(pin[GPIODriver.FALLING_NAME],
                                             "GPIO")
            # rising edge
                if pin[GPIODriver.PRESSED] is False and pin[
                        GPIODriver.LAST] is True:
                    pin[GPIODriver.LAST] = pin[GPIODriver.PRESSED]
                    pin[GPIODriver.REPEAT_COUNT] = pin[GPIODriver.REPEAT]
                    if pin[GPIODriver.
                           RISING_NAME] != '' and self.button_callback is not None:
                        self.button_callback(pin[GPIODriver.RISING_NAME],
                                             "GPIO")

                # do state callbacks
                if pin[GPIODriver.REPEAT_COUNT] == 0:
                    if pin[GPIODriver.ZERO_NAME] != '' and pin[
                            GPIODriver.
                            PRESSED] is True and self.button_callback is not None:
                        self.button_callback(pin[GPIODriver.ZERO_NAME], "GPIO")
                    if pin[GPIODriver.ONE_NAME] != '' and pin[
                            GPIODriver.
                            PRESSED] is False and self.button_callback is not None:
                        self.button_callback(pin[GPIODriver.ONE_NAME], "GPIO")
                    pin[GPIODriver.REPEAT_COUNT] = pin[GPIODriver.REPEAT]
                else:
                    if pin[GPIODriver.REPEAT] != -1:
                        pin[GPIODriver.REPEAT_COUNT] -= 1

    # execute an output event
    def handle_output_event(self, name, param_type, param_values, req_time):
        if GPIODriver.gpio_enabled is False:
            return 'normal', 'gpio not enabled'

        #gpio only handles state parameters
        if param_type != 'state':
            return 'error', 'gpio does not handle: ' + param_type
        to_state = param_values[0]
        if to_state == 'on':
            state = True
        else:
            state = False

        pin = self.output_pin_of(name)
        if pin == -1:
            return 'error', 'Not an output for gpio: ' + name

        self.mon.log(
            self,
            'pin P1-' + str(pin) + ' set  ' + str(state) + ' required at: ' +
            str(req_time) + ' sent at: ' + str(long(time.time())))
        # print 'pin P1-'+ str(pin)+ ' set  '+ str(state) + ' required: ' + str(req_time)+ ' actual: ' + str(long(time.time()))
        self.GPIO.output(pin, state)
        return 'normal', 'gpio handled OK'

# ************************************************
# gpio output interface methods
# these can be called from many classes so need to operate on class variables
# ************************************************

    def reset_outputs(self):
        if GPIODriver.gpio_enabled is True:
            self.mon.log(self, 'reset outputs')
            for index, pin in enumerate(GPIODriver.pins):
                num = pin[GPIODriver.PIN]
                if pin[GPIODriver.DIRECTION] == 'out':
                    self.GPIO.output(num, False)

# ************************************************
# internal functions
# these can be called from many classes so need to operate on class variables
# ************************************************

    def output_pin_of(self, name):
        for pin in GPIODriver.pins:
            # print " in list" + pin[GPIODriver.NAME] + str(pin[GPIODriver.PIN] )
            if pin[GPIODriver.NAME] == name and pin[
                    GPIODriver.DIRECTION] == 'out':
                return pin[GPIODriver.PIN]
        return -1

# ***********************************
# reading gpio.cfg
# ************************************

    def read(self, pp_dir, pp_home, pp_profile):
        # try inside profile
        filename = pp_profile + os.sep + 'pp_io_config' + os.sep + 'gpio.cfg'
        if os.path.exists(filename):
            self.config = ConfigParser.ConfigParser()
            self.config.read(filename)
            self.mon.log(self, "gpio.cfg read from " + filename)
            return 'normal', 'gpio.cfg read'
        else:
            return 'normal', 'gpio.cfg not found in profile: ' + filename
class OSCDriver(object):

    # executed by main program
    def init(self, pp_profile, manager_unit, preferred_interface, my_ip,
             show_command_callback, input_event_callback, animate_callback):

        self.pp_profile = pp_profile
        self.show_command_callback = show_command_callback
        self.input_event_callback = input_event_callback
        self.animate_callback = animate_callback

        self.mon = Monitor()
        config_file = self.pp_profile + os.sep + 'pp_io_config' + os.sep + 'osc.cfg'
        if not os.path.exists(config_file):
            self.mon.err(self,
                         'OSC Configuration file not found: ' + config_file)
            return 'error', 'OSC Configuration file nof found: ' + config_file

        self.mon.log(self, 'OSC Configuration file found at: ' + config_file)
        self.osc_config = OSCConfig()

        # reads config data
        if self.osc_config.read(config_file) == False:
            return 'error', 'failed to read osc.cfg'

        # unpack config data and initialise

        if self.osc_config.this_unit_name == '':
            return 'error', 'OSC Config -  This Unit has no name'
        if len(self.osc_config.this_unit_name.split()) > 1:
            return 'error', 'OSC config - This Unit Name not a single word: ' + self.osc_config.this_unit_name
        self.this_unit_name = self.osc_config.this_unit_name

        if self.osc_config.this_unit_ip == '':
            self.this_unit_ip = my_ip
        else:
            self.this_unit_ip = self.osc_config.this_unit_ip

        if self.osc_config.slave_enabled == 'yes':
            if not self.osc_config.listen_port.isdigit():
                return 'error', 'OSC Config - Listen port is not a positve number: ' + self.osc_config.listen_port
            self.listen_port = self.osc_config.listen_port

        if self.osc_config.master_enabled == 'yes':
            if not self.osc_config.reply_listen_port.isdigit():
                return 'error', 'OSC Config - Reply Listen port is not a positve number: ' + self.osc_config.reply_listen_port
            self.reply_listen_port = self.osc_config.reply_listen_port

            # prepare the list of slaves
            status, message = self.parse_slaves()
            if status == 'error':
                return status, message

        self.prefix = '/pipresents'
        self.this_unit = '/' + self.this_unit_name

        self.input_server = None
        self.input_reply_client = None
        self.input_st = None

        self.output_client = None
        self.output_reply_server = None
        self.output_reply_st = None

        if self.osc_config.slave_enabled == 'yes' and self.osc_config.master_enabled == 'yes' and self.listen_port == self.reply_listen_port:
            # The two listen ports are the same so use one server for input and output

            #start the client that sends commands to the slaves
            self.output_client = OSC.OSCClient()
            self.mon.log(
                self, 'sending commands to slaves and replies to master on: ' +
                self.reply_listen_port)

            #start the input+output reply server
            self.mon.log(
                self,
                'listen to commands and replies from slave units using: ' +
                self.this_unit_ip + ':' + self.reply_listen_port)
            self.output_reply_server = myOSCServer(
                (self.this_unit_ip, int(self.reply_listen_port)),
                self.output_client)
            self.add_default_handler(self.output_reply_server)
            self.add_input_handlers(self.output_reply_server)
            self.add_output_reply_handlers(self.output_reply_server)

            self.input_server = self.output_reply_server

        else:

            if self.osc_config.slave_enabled == 'yes':
                # we want this to be a slave to something else

                # start the client that sends replies to controlling unit
                self.input_reply_client = OSC.OSCClient()

                #start the input server
                self.mon.log(
                    self, 'listening to commands on: ' + self.this_unit_ip +
                    ':' + self.listen_port)
                self.input_server = myOSCServer(
                    (self.this_unit_ip, int(self.listen_port)),
                    self.input_reply_client)
                self.add_default_handler(self.input_server)
                self.add_input_handlers(self.input_server)
                # print self.pretty_list(self.input_server.getOSCAddressSpace(),'\n')

            if self.osc_config.master_enabled == 'yes':
                #we want to control other units

                #start the client that sends commands to the slaves
                self.output_client = OSC.OSCClient()
                self.mon.log(
                    self, 'sending commands to slaves on port: ' +
                    self.reply_listen_port)

                #start the output reply server
                self.mon.log(
                    self, 'listen to replies from slave units using: ' +
                    self.this_unit_ip + ':' + self.reply_listen_port)
                self.output_reply_server = myOSCServer(
                    (self.this_unit_ip, int(self.reply_listen_port)),
                    self.output_client)
                self.add_default_handler(self.output_reply_server)
                self.add_output_reply_handlers(self.output_reply_server)

        return 'normal', 'osc.cfg read'

    def terminate(self):
        if self.input_server != None:
            self.input_server.close()
        if self.output_reply_server != None:
            self.output_reply_server.close()
        self.mon.log(self, 'Waiting for Server threads to finish')
        if self.input_st != None:
            self.input_st.join()  ##!!!
        if self.output_reply_st != None:
            self.output_reply_st.join()  ##!!!
        self.mon.log(self, 'server threads closed')
        if self.input_reply_client != None:
            self.input_reply_client.close()
        if self.output_client != None:
            self.output_client.close()

    def start_server(self):
        # Start input Server
        self.mon.log(self, 'Starting input OSCServer')
        if self.input_server != None:
            self.input_st = threading.Thread(
                target=self.input_server.serve_forever)
            self.input_st.start()

        # Start output_reply server
        self.mon.log(self, 'Starting output reply OSCServer')
        if self.output_reply_server != None:
            self.output_reply_st = threading.Thread(
                target=self.output_reply_server.serve_forever)
            self.output_reply_st.start()

    def parse_slaves(self):
        name_list = self.osc_config.slave_units_name.split()
        ip_list = self.osc_config.slave_units_ip.split()
        if len(name_list) == 0:
            return 'error', 'OSC Config - List of slaves name is empty'
        if len(name_list) != len(ip_list):
            return 'error', 'OSC Config - Lengths of list of slaves name and slaves IP is different'
        self.slave_name_list = []
        self.slave_ip_list = []
        for i, name in enumerate(name_list):
            self.slave_name_list.append(name)
            self.slave_ip_list.append(ip_list[i])
        return 'normal', 'slaves parsed'

    def parse_osc_command(self, fields):
        # send message to slave unit - INTERFACE WITH pipresents
        if len(fields) < 2:
            return 'error', 'too few fields in OSC command ' + ' '.join(fields)
        to_unit_name = fields[0]
        show_command = fields[1]
        # print 'FIELDS ',fields

        # send an arbitary osc message
        if show_command == 'send':
            if len(fields) > 2:
                osc_address = fields[2]
                arg_list = []
                if len(fields) > 3:
                    arg_list = fields[3:]
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('open', 'close', 'openexclusive'):
            if len(fields) == 3:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]
            else:
                return 'error', 'OSC - wrong number of fields in ' + ' '.join(
                    fields)

        elif show_command == 'monitor':
            if fields[2] in ('on', 'off'):
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]
            else:
                self.mon.err(
                    self,
                    'OSC - illegal state in ' + show_command + ' ' + fields[2])

        elif show_command == 'event':
            if len(fields) == 3:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = [fields[2]]

        elif show_command == 'animate':
            if len(fields) > 2:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = fields[2:]
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('closeall', 'exitpipresents', 'shutdownnow',
                              'reboot'):
            if len(fields) == 2:
                osc_address = self.prefix + '/' + to_unit_name + '/core/' + show_command
                arg_list = []
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        elif show_command in ('loopback', 'server-info'):
            if len(fields) == 2:
                osc_address = self.prefix + '/' + to_unit_name + '/system/' + show_command
                arg_list = []
            else:
                return 'error', 'OSC - wrong nmber of fields in ' + ' '.join(
                    fields)

        else:
            return 'error', 'OSC - unkown command in ' + ' '.join(fields)

        ip = self.find_ip(to_unit_name, self.slave_name_list,
                          self.slave_ip_list)
        if ip == '':
            return 'warn', 'OSC Unit Name not in the list of slaves: ' + to_unit_name
        self.sendto(ip, osc_address, arg_list)
        return 'normal', 'osc command sent'

    def find_ip(self, name, name_list, ip_list):
        i = 0
        for j in name_list:
            if j == name:
                break
            i = i + 1

        if i == len(name_list):
            return ''
        else:
            return ip_list[i]

    def sendto(self, ip, osc_address, arg_list):
        # print ip,osc_address,arg_list
        if self.output_client is None:
            self.mon.warn(self, 'Master not enabled, ignoring OSC command')
            return
        msg = OSC.OSCMessage()
        # print address
        msg.setAddress(osc_address)
        for arg in arg_list:
            # print arg
            msg.append(arg)

        try:
            self.output_client.sendto(msg, (ip, int(self.reply_listen_port)))
            self.mon.log(
                self,
                'Sent OSC command: ' + osc_address + ' ' + ' '.join(arg_list) +
                ' to ' + ip + ':' + self.reply_listen_port)
        except Exception as e:
            self.mon.warn(
                self, 'error in client when sending OSC command: ' + str(e))

# **************************************
# Handlers for fallback
# **************************************

    def add_default_handler(self, server):
        server.addMsgHandler('default', self.no_match_handler)

    def no_match_handler(self, addr, tags, stuff, source):
        text = "No handler for message from %s" % OSC.getUrlStr(source) + '\n'
        text += "     %s" % addr + self.pretty_list(stuff, '')
        self.mon.warn(self, text)
        return None

# **************************************
# Handlers for Slave (input)
# **************************************

    def add_input_handlers(self, server):
        server.addMsgHandler(
            self.prefix + self.this_unit + "/system/server-info",
            self.server_info_handler)
        server.addMsgHandler(self.prefix + self.this_unit + "/system/loopback",
                             self.loopback_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/open',
                             self.open_show_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/close',
                             self.close_show_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/openexclusive',
            self.openexclusive_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/closeall',
                             self.closeall_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/exitpipresents',
            self.exitpipresents_handler)
        server.addMsgHandler(
            self.prefix + self.this_unit + '/core/shutdownnow',
            self.shutdownnow_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/reboot',
                             self.reboot_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/event',
                             self.input_event_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/animate',
                             self.animate_handler)
        server.addMsgHandler(self.prefix + self.this_unit + '/core/monitor',
                             self.monitor_handler)

    # reply to master unit with name of this unit and commands
    def server_info_handler(self, addr, tags, stuff, source):

        msg = OSC.OSCMessage(self.prefix + '/system/server-info-reply')
        msg.append(self.this_unit_name)
        msg.append(self.input_server.getOSCAddressSpace())
        self.mon.log(self,
                     'Sent Server Info reply to %s:' % OSC.getUrlStr(source))
        return msg

    # reply to master unit with a loopback message
    def loopback_handler(self, addr, tags, stuff, source):
        msg = OSC.OSCMessage(self.prefix + '/system/loopback-reply')
        self.mon.log(self,
                     'Sent loopback reply to %s:' % OSC.getUrlStr(source))
        return msg

    def open_show_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('open ', args, 1)

    def openexclusive_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('openexclusive ', args, 1)

    def close_show_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('close ', args, 1)

    def closeall_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('closeall', args, 0)

    def monitor_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('monitor ', args, 1)

    def exitpipresents_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('exitpipresents', args, 0)

    def reboot_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('reboot', args, 0)

    def shutdownnow_handler(self, address, tags, args, source):
        self.prepare_show_command_callback('shutdownnow', args, 0)

    def prepare_show_command_callback(self, command, args, limit):
        if len(args) == limit:
            if limit != 0:
                self.mon.sched(self, TimeOfDay.now,
                               'Received from OSC: ' + command + ' ' + args[0])
                self.show_command_callback(command + args[0])
            else:
                self.mon.sched(self, TimeOfDay.now,
                               'Received from OSC: ' + command)
                self.show_command_callback(command)
        else:
            self.mon.warn(
                self, 'OSC show command does not have ' + limit +
                ' argument - ignoring')

    def input_event_handler(self, address, tags, args, source):
        if len(args) == 1:
            self.input_event_callback(args[0], 'OSC')
        else:
            self.mon.warn(
                self, 'OSC input event does not have 1 argument - ignoring')

    def animate_handler(self, address, tags, args, source):
        if len(args) != 0:
            # delay symbol,param_type,param_values,req_time as a string
            text = '0 '
            for arg in args:
                text = text + arg + ' '
            text = text + '0'
            # print text
            self.animate_callback(text)
        else:
            self.mon.warn(self, 'OSC output event has no arguments - ignoring')


# **************************************
# Handlers for Master- replies from slaves (output)
# **************************************

# reply handlers do not have the destinatuion unit in the address as they are always sent to the originator

    def add_output_reply_handlers(self, server):
        server.addMsgHandler(self.prefix + "/system/server-info-reply",
                             self.server_info_reply_handler)
        server.addMsgHandler(self.prefix + "/system/loopback-reply",
                             self.loopback_reply_handler)

    # print result of info request from slave unit
    def server_info_reply_handler(self, addr, tags, stuff, source):
        self.mon.log(
            self, 'server info reply from slave ' + OSC.getUrlStr(source) +
            self.pretty_list(stuff, '\n'))
        print 'Received reply to Server-Info command from slave: ', OSC.getUrlStr(
            source), self.pretty_list(stuff, '\n')
        return None

    #print result of info request from slave unit
    def loopback_reply_handler(self, addr, tags, stuff, source):
        self.mon.log(
            self, 'server info reply from slave ' + OSC.getUrlStr(source) +
            self.pretty_list(stuff, '\n'))
        print 'Received reply to Loopback command from slave: ' + OSC.getUrlStr(
            source) + ' ' + self.pretty_list(stuff, '\n')
        return None

    def pretty_list(self, fields, separator):
        text = ' '
        for field in fields:
            text += str(field) + separator
        return text + '\n'
Example #30
0
class PPEditor:

    # ***************************************
    # INIT
    # ***************************************

    def __init__(self):

        self.editor_issue = "1.2"

        # get command options
        self.command_options = ed_options()

        # get directory holding the code
        self.pp_dir = sys.path[0]

        if not os.path.exists(self.pp_dir + os.sep + "pp_editor.py"):
            tkMessageBox.showwarning("Pi Presents",
                                     "Bad Application Directory")
            exit()

#Initialise logging
        Monitor.log_path = self.pp_dir
        self.mon = Monitor()
        self.mon.on()

        if self.command_options['debug'] == True:
            Monitor.global_enable = True
        else:
            Monitor.global_enable = False

        self.mon.log(self, "Pi Presents Editor is starting")

        self.mon.log(self, " OS and separator " + os.name + '  ' + os.sep)
        self.mon.log(self,
                     "sys.path[0] -  location of code: code " + sys.path[0])

        # set up the gui

        #root is the Tkinter root widget
        self.root = tk.Tk()
        self.root.title("Editor for Pi Presents")

        # self.root.configure(background='grey')

        self.root.resizable(False, False)

        #define response to main window closing
        self.root.protocol("WM_DELETE_WINDOW", self.app_exit)

        # bind some display fields
        self.filename = tk.StringVar()
        self.display_selected_track_title = tk.StringVar()
        self.display_show = tk.StringVar()

        # define menu
        menubar = Menu(self.root)

        profilemenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        profilemenu.add_command(label='Open',
                                command=self.open_existing_profile)
        profilemenu.add_command(label='Validate',
                                command=self.validate_profile)
        menubar.add_cascade(label='Profile', menu=profilemenu)

        ptypemenu = Menu(profilemenu, tearoff=0, bg="grey", fg="black")
        ptypemenu.add_command(label='Exhibit',
                              command=self.new_exhibit_profile)
        ptypemenu.add_command(label='Media Show',
                              command=self.new_mediashow_profile)
        ptypemenu.add_command(label='Menu', command=self.new_menu_profile)
        ptypemenu.add_command(label='Presentation',
                              command=self.new_presentation_profile)
        ptypemenu.add_command(label='Interactive',
                              command=self.new_interactive_profile)
        ptypemenu.add_command(label='Live Show',
                              command=self.new_liveshow_profile)
        ptypemenu.add_command(label='RadioButton Show',
                              command=self.new_radiobuttonshow_profile)
        ptypemenu.add_command(label='Hyperlink Show',
                              command=self.new_hyperlinkshow_profile)
        ptypemenu.add_command(label='Blank', command=self.new_blank_profile)
        profilemenu.add_cascade(label='New from Template', menu=ptypemenu)

        showmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        showmenu.add_command(label='Delete', command=self.remove_show)
        showmenu.add_command(label='Edit', command=self.m_edit_show)
        showmenu.add_command(label='Copy To', command=self.copy_show)
        menubar.add_cascade(label='Show', menu=showmenu)

        stypemenu = Menu(showmenu, tearoff=0, bg="grey", fg="black")
        stypemenu.add_command(label='Menu', command=self.add_menu)
        stypemenu.add_command(label='MediaShow', command=self.add_mediashow)
        stypemenu.add_command(label='LiveShow', command=self.add_liveshow)
        stypemenu.add_command(label='HyperlinkShow',
                              command=self.add_hyperlinkshow)
        stypemenu.add_command(label='RadioButtonShow',
                              command=self.add_radiobuttonshow)
        showmenu.add_cascade(label='Add', menu=stypemenu)

        medialistmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='MediaList', menu=medialistmenu)
        medialistmenu.add_command(label='Add', command=self.add_medialist)
        medialistmenu.add_command(label='Delete',
                                  command=self.remove_medialist)

        trackmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        trackmenu.add_command(label='Delete', command=self.remove_track)
        trackmenu.add_command(label='Edit', command=self.m_edit_track)
        trackmenu.add_command(label='Add from Dir',
                              command=self.add_tracks_from_dir)
        trackmenu.add_command(label='Add from File',
                              command=self.add_track_from_file)

        menubar.add_cascade(label='Track', menu=trackmenu)

        typemenu = Menu(trackmenu, tearoff=0, bg="grey", fg="black")
        typemenu.add_command(label='Video', command=self.new_video_track)
        typemenu.add_command(label='Audio', command=self.new_audio_track)
        typemenu.add_command(label='Image', command=self.new_image_track)
        typemenu.add_command(label='Web', command=self.new_web_track)
        typemenu.add_command(label='Message', command=self.new_message_track)
        typemenu.add_command(label='Show', command=self.new_show_track)
        typemenu.add_command(label='Menu Background',
                             command=self.new_menu_background_track)
        typemenu.add_command(label='Child Show',
                             command=self.new_child_show_track)
        trackmenu.add_cascade(label='New', menu=typemenu)

        toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Tools', menu=toolsmenu)
        toolsmenu.add_command(label='Update All', command=self.update_all)

        optionsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Options', menu=optionsmenu)
        optionsmenu.add_command(label='Edit', command=self.edit_options)

        helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Help', menu=helpmenu)
        helpmenu.add_command(label='Help', command=self.show_help)
        helpmenu.add_command(label='About', command=self.about)

        self.root.config(menu=menubar)

        top_frame = Frame(self.root)
        top_frame.pack(side=TOP)
        bottom_frame = Frame(self.root)
        bottom_frame.pack(side=TOP, fill=BOTH, expand=1)

        left_frame = Frame(bottom_frame, padx=5)
        left_frame.pack(side=LEFT)
        middle_frame = Frame(bottom_frame, padx=5)
        middle_frame.pack(side=LEFT)
        right_frame = Frame(bottom_frame, padx=5, pady=10)
        right_frame.pack(side=LEFT)
        updown_frame = Frame(bottom_frame, padx=5)
        updown_frame.pack(side=LEFT)

        tracks_title_frame = Frame(right_frame)
        tracks_title_frame.pack(side=TOP)
        tracks_label = Label(tracks_title_frame,
                             text="Tracks in Selected Medialist")
        tracks_label.pack()
        tracks_frame = Frame(right_frame)
        tracks_frame.pack(side=TOP)
        shows_title_frame = Frame(left_frame)
        shows_title_frame.pack(side=TOP)
        shows_label = Label(shows_title_frame, text="Shows")
        shows_label.pack()
        shows_frame = Frame(left_frame)
        shows_frame.pack(side=TOP)
        shows_title_frame = Frame(left_frame)
        shows_title_frame.pack(side=TOP)
        medialists_title_frame = Frame(left_frame)
        medialists_title_frame.pack(side=TOP)
        medialists_label = Label(medialists_title_frame, text="Medialists")
        medialists_label.pack()
        medialists_frame = Frame(left_frame)
        medialists_frame.pack(side=LEFT)

        # define buttons

        add_button = Button(middle_frame,
                            width=5,
                            height=2,
                            text='Edit\nShow',
                            fg='black',
                            command=self.m_edit_show,
                            bg="light grey")
        add_button.pack(side=RIGHT)

        add_button = Button(updown_frame,
                            width=5,
                            height=1,
                            text='Add',
                            fg='black',
                            command=self.add_track_from_file,
                            bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame,
                            width=5,
                            height=1,
                            text='Edit',
                            fg='black',
                            command=self.m_edit_track,
                            bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame,
                            width=5,
                            height=1,
                            text='Up',
                            fg='black',
                            command=self.move_track_up,
                            bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame,
                            width=5,
                            height=1,
                            text='Down',
                            fg='black',
                            command=self.move_track_down,
                            bg="light grey")
        add_button.pack(side=TOP)

        # define display of showlist
        scrollbar = Scrollbar(shows_frame, orient=tk.VERTICAL)
        self.shows_display = Listbox(shows_frame,
                                     selectmode=SINGLE,
                                     height=7,
                                     width=40,
                                     bg="white",
                                     activestyle=NONE,
                                     fg="black",
                                     yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.shows_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.shows_display.pack(side=LEFT, fill=BOTH, expand=1)
        self.shows_display.bind("<ButtonRelease-1>", self.e_select_show)

        # define display of medialists
        scrollbar = Scrollbar(medialists_frame, orient=tk.VERTICAL)
        self.medialists_display = Listbox(medialists_frame,
                                          selectmode=SINGLE,
                                          height=7,
                                          width=40,
                                          bg="white",
                                          activestyle=NONE,
                                          fg="black",
                                          yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.medialists_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.medialists_display.pack(side=LEFT, fill=BOTH, expand=1)
        self.medialists_display.bind("<ButtonRelease-1>",
                                     self.select_medialist)

        # define display of tracks
        scrollbar = Scrollbar(tracks_frame, orient=tk.VERTICAL)
        self.tracks_display = Listbox(tracks_frame,
                                      selectmode=SINGLE,
                                      height=15,
                                      width=40,
                                      bg="white",
                                      activestyle=NONE,
                                      fg="black",
                                      yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.tracks_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.tracks_display.pack(side=LEFT, fill=BOTH, expand=1)
        self.tracks_display.bind("<ButtonRelease-1>", self.e_select_track)

        # initialise editor options class
        self.options = Options(
            self.pp_dir)  #creates options file in code directory if necessary

        # initialise variables
        self.init()

        #and enter Tkinter event loop
        self.root.mainloop()

# ***************************************
# INIT AND EXIT
# ***************************************

    def app_exit(self):
        self.root.destroy()
        exit()

    def init(self):
        self.options.read()
        self.pp_home_dir = self.options.pp_home_dir
        self.initial_media_dir = self.options.initial_media_dir
        self.mon.log(self, "Data Home from options is " + self.pp_home_dir)
        self.mon.log(self,
                     "Initial Media from options is " + self.initial_media_dir)
        self.current_medialist = None
        self.current_showlist = None
        self.current_show = None
        self.shows_display.delete(0, END)
        self.medialists_display.delete(0, END)
        self.tracks_display.delete(0, END)

# ***************************************
# MISCELLANEOUS
# ***************************************

    def edit_options(self):
        """edit the options then read them from file"""
        eo = OptionsDialog(self.root, self.options.options_file,
                           'Edit Options')
        if eo.result == True: self.init()

    def show_help(self):
        tkMessageBox.showinfo("Help", "Read 'manual.pdf'")

    def about(self):
        tkMessageBox.showinfo(
            "About",
            "Editor for Pi Presents Profiles\n" + "For profile version: " +
            self.editor_issue + "\nAuthor: Ken Thompson" +
            "\nWebsite: http://pipresents.wordpress.com/")

    def validate_profile(self):
        val = Validator()
        val.validate_profile(self.root, self.pp_dir, self.pp_home_dir,
                             self.pp_profile_dir, self.editor_issue, True)

# **************
# PROFILES
# **************

    def open_existing_profile(self):
        initial_dir = self.pp_home_dir + os.sep + "pp_profiles"
        if os.path.exists(initial_dir) == False:
            self.mon.err(
                self, "Home directory not found: " + initial_dir +
                "\n\nHint: Data Home option must end in pp_home")
            return
        dir_path = tkFileDialog.askdirectory(initialdir=initial_dir)
        # dir_path="C:\Users\Ken\pp_home\pp_profiles\\ttt"
        if len(dir_path) > 0:
            self.open_profile(dir_path)

    def open_profile(self, dir_path):
        showlist_file = dir_path + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file) == False:
            self.mon.err(
                self, "Not a Profile: " + dir_path +
                "\n\nHint: Have you opened the profile directory?")
            return
        self.pp_profile_dir = dir_path
        self.root.title("Editor for Pi Presents - " + self.pp_profile_dir)
        if self.open_showlist(self.pp_profile_dir) == False:
            self.init()
            return
        self.open_medialists(self.pp_profile_dir)
        self.refresh_tracks_display()

    def new_profile(self, profile):
        d = Edit1Dialog(self.root, "New Profile", "Name", "")
        if d.result == None:
            return
        name = str(d.result)
        if name == "":
            tkMessageBox.showwarning("New Profile", "Name is blank")
            return
        to = self.pp_home_dir + os.sep + "pp_profiles" + os.sep + name
        if os.path.exists(to) == True:
            tkMessageBox.showwarning("New Profile",
                                     "Profile exists\n(%s)" % to)
            return
        shutil.copytree(profile, to, symlinks=False, ignore=None)
        self.open_profile(to)

    def new_exhibit_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_exhibit"
        self.new_profile(profile)

    def new_interactive_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_interactive"
        self.new_profile(profile)

    def new_menu_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_menu"
        self.new_profile(profile)

    def new_presentation_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_presentation"
        self.new_profile(profile)

    def new_blank_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_blank"
        self.new_profile(profile)

    def new_mediashow_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_mediashow"
        self.new_profile(profile)

    def new_liveshow_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_liveshow"
        self.new_profile(profile)

    def new_radiobuttonshow_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_radiobuttonshow"
        self.new_profile(profile)

    def new_hyperlinkshow_profile(self):
        profile = self.pp_dir + "/pp_home/pp_profiles/ppt_hyperlinkshow"
        self.new_profile(profile)

# ***************************************
# Shows
# ***************************************

    def open_showlist(self, dir):
        showlist_file = dir + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file) == False:
            self.mon.err(
                self, "showlist file not found at " + dir +
                "\n\nHint: Have you opened the profile directory?")
            self.app_exit()
        self.current_showlist = ShowList()
        self.current_showlist.open_json(showlist_file)
        if float(self.current_showlist.sissue()) < float(
                self.editor_issue) or (
                    self.command_options['forceupdate'] == True
                    and float(self.current_showlist.sissue()) == float(
                        self.editor_issue)):
            self.update_profile()
            self.mon.err(
                self, "Version of profile has been updated to " +
                self.editor_issue + ", please re-open")
            return False
        if float(self.current_showlist.sissue()) > float(self.editor_issue):
            self.mon.err(
                self, "Version of profile is greater than editor, must exit")
            self.app_exit()
        self.refresh_shows_display()
        return True

    def save_showlist(self, dir):
        if self.current_showlist <> None:
            showlist_file = dir + os.sep + "pp_showlist.json"
            self.current_showlist.save_list(showlist_file)

    def add_eventshow(self):
        self.add_show(PPdefinitions.new_shows['eventshow'])

    def add_mediashow(self):
        self.add_show(PPdefinitions.new_shows['mediashow'])

    def add_liveshow(self):
        self.add_show(PPdefinitions.new_shows['liveshow'])

    def add_radiobuttonshow(self):
        self.add_show(PPdefinitions.new_shows['radiobuttonshow'])

    def add_hyperlinkshow(self):
        self.add_show(PPdefinitions.new_shows['hyperlinkshow'])

    def add_menu(self):
        self.add_show(PPdefinitions.new_shows['menu'])

    def add_start(self):
        self.add_show(PPdefinitions.new_shows['start'])

    def add_show(self, default):
        # append it to the showlist and then add the medialist
        if self.current_showlist <> None:
            d = Edit1Dialog(self.root, "AddShow", "Show Reference", "")
            if d.result == None:
                return
            name = str(d.result)
            if name == "":
                tkMessageBox.showwarning("Add Show", "Name is blank")
                return
            if self.current_showlist.index_of_show(name) <> -1:
                tkMessageBox.showwarning(
                    "Add Show", "A Show with this name already exists")
                return
            copied_show = self.current_showlist.copy(default, name)
            mediafile = self.add_medialist(name)
            if mediafile <> '':
                copied_show['medialist'] = mediafile
            self.current_showlist.append(copied_show)
            self.save_showlist(self.pp_profile_dir)
            self.refresh_shows_display()

    def remove_show(self):
        if self.current_showlist <> None and self.current_showlist.length(
        ) > 0 and self.current_showlist.show_is_selected():
            if tkMessageBox.askokcancel("Delete Show", "Delete Show"):
                index = self.current_showlist.selected_show_index()
                self.current_showlist.remove(index)
                self.save_showlist(self.pp_profile_dir)
                self.refresh_shows_display()

    def show_refs(self):
        _show_refs = []
        for index in range(self.current_showlist.length()):
            if self.current_showlist.show(index)['show-ref'] <> "start":
                _show_refs.append(
                    copy.deepcopy(
                        self.current_showlist.show(index)['show-ref']))
        return _show_refs

    def refresh_shows_display(self):
        self.shows_display.delete(0, self.shows_display.size())
        for index in range(self.current_showlist.length()):
            self.shows_display.insert(
                END,
                self.current_showlist.show(index)['title'] + "   [" +
                self.current_showlist.show(index)['show-ref'] + "]")
        if self.current_showlist.show_is_selected():
            self.shows_display.itemconfig(
                self.current_showlist.selected_show_index(), fg='red')
            self.shows_display.see(self.current_showlist.selected_show_index())

    def e_select_show(self, event):
        if self.current_showlist <> None and self.current_showlist.length(
        ) > 0:
            mouse_item_index = int(event.widget.curselection()[0])
            self.current_showlist.select(mouse_item_index)
            self.refresh_shows_display()

    def copy_show(self):
        if self.current_showlist <> None and self.current_showlist.show_is_selected(
        ):
            self.add_show(self.current_showlist.selected_show())

    def m_edit_show(self):
        self.edit_show(PPdefinitions.show_types,
                       PPdefinitions.show_field_specs)

    def edit_show(self, show_types, field_specs):
        if self.current_showlist <> None and self.current_showlist.show_is_selected(
        ):
            d = EditItem(self.root, "Edit Show",
                         self.current_showlist.selected_show(), show_types,
                         field_specs, self.show_refs(), self.initial_media_dir,
                         self.pp_home_dir, 'show')
            if d.result == True:

                self.save_showlist(self.pp_profile_dir)
                self.refresh_shows_display()

# ***************************************
#   Medialists
# ***************************************

    def open_medialists(self, dir):
        self.medialists = []
        for file in os.listdir(dir):
            if file.endswith(".json") and file <> 'pp_showlist.json':
                self.medialists = self.medialists + [file]
        self.medialists_display.delete(0, self.medialists_display.size())
        for index in range(len(self.medialists)):
            self.medialists_display.insert(END, self.medialists[index])
        self.current_medialists_index = -1
        self.current_medialist = None

    def add_medialist(self, name=None):
        if name == None:
            d = Edit1Dialog(self.root, "Add Medialist", "File", "")
            if d.result == None:
                return ''
            name = str(d.result)
            if name == "":
                tkMessageBox.showwarning("Add medialist", "Name is blank")
                return ''

        if not name.endswith(".json"):
            name = name + (".json")

        path = self.pp_profile_dir + os.sep + name
        if os.path.exists(path) == True:
            tkMessageBox.showwarning("Add medialist",
                                     "Medialist file exists\n(%s)" % path)
            return ''
        nfile = open(path, 'wb')
        nfile.write("{")
        nfile.write("\"issue\":  \"" + self.editor_issue + "\",\n")
        nfile.write("\"tracks\": [")
        nfile.write("]")
        nfile.write("}")
        nfile.close()
        # append it to the list
        self.medialists.append(copy.deepcopy(name))
        # add title to medialists display
        self.medialists_display.insert(END, name)
        # and set it as the selected medialist
        self.refresh_medialists_display()
        return name

    def remove_medialist(self):
        if self.current_medialist <> None:
            if tkMessageBox.askokcancel("Delete Medialist",
                                        "Delete Medialist"):
                os.remove(self.pp_profile_dir + os.sep +
                          self.medialists[self.current_medialists_index])
                self.open_medialists(self.pp_profile_dir)
                self.refresh_medialists_display()
                self.refresh_tracks_display()

    def select_medialist(self, event):
        """
        user clicks on a medialst in a profile so try and select it.
        """
        # needs forgiving int for possible tkinter upgrade
        if len(self.medialists) > 0:
            self.current_medialists_index = int(event.widget.curselection()[0])
            self.current_medialist = MediaList('ordered')
            if not self.current_medialist.open_list(
                    self.pp_profile_dir + os.sep +
                    self.medialists[self.current_medialists_index],
                    self.current_showlist.sissue()):
                self.mon.err(
                    self, "medialist is a different version to showlist: " +
                    self.medialists[self.current_medialists_index])
                self.app_exit()
            self.refresh_tracks_display()
            self.refresh_medialists_display()

    def refresh_medialists_display(self):
        self.medialists_display.delete(0, len(self.medialists))
        for index in range(len(self.medialists)):
            self.medialists_display.insert(END, self.medialists[index])
        if self.current_medialist <> None:
            self.medialists_display.itemconfig(self.current_medialists_index,
                                               fg='red')
            self.medialists_display.see(self.current_medialists_index)

    def save_medialist(self):
        basefile = self.medialists[self.current_medialists_index]
        #print type(basefile)
        # basefile=str(basefile)
        #print type(basefile)
        file = self.pp_profile_dir + os.sep + basefile
        self.current_medialist.save_list(file)

# ***************************************
#   Tracks
# ***************************************

    def refresh_tracks_display(self):
        self.tracks_display.delete(0, self.tracks_display.size())
        if self.current_medialist <> None:
            for index in range(self.current_medialist.length()):
                if self.current_medialist.track(index)['track-ref'] <> "":
                    track_ref_string = "  [" + self.current_medialist.track(
                        index)['track-ref'] + "]"
                else:
                    track_ref_string = ""
                self.tracks_display.insert(
                    END,
                    self.current_medialist.track(index)['title'] +
                    track_ref_string)
            if self.current_medialist.track_is_selected():
                self.tracks_display.itemconfig(
                    self.current_medialist.selected_track_index(), fg='red')
                self.tracks_display.see(
                    self.current_medialist.selected_track_index())

    def e_select_track(self, event):
        if self.current_medialist <> None and self.current_medialist.length(
        ) > 0:
            mouse_item_index = int(event.widget.curselection()[0])
            self.current_medialist.select(mouse_item_index)
            self.refresh_tracks_display()

    def m_edit_track(self):
        self.edit_track(PPdefinitions.track_types,
                        PPdefinitions.track_field_specs)

    def edit_track(self, track_types, field_specs):
        if self.current_medialist <> None and self.current_medialist.track_is_selected(
        ):
            d = EditItem(self.root, "Edit Track",
                         self.current_medialist.selected_track(), track_types,
                         field_specs, self.show_refs(), self.initial_media_dir,
                         self.pp_home_dir, 'track')
            if d.result == True:
                self.save_medialist()
            self.refresh_tracks_display()

    def move_track_up(self):
        if self.current_medialist <> None and self.current_medialist.track_is_selected(
        ):
            self.current_medialist.move_up()
            self.refresh_tracks_display()
            self.save_medialist()

    def move_track_down(self):
        if self.current_medialist <> None and self.current_medialist.track_is_selected(
        ):
            self.current_medialist.move_down()
            self.refresh_tracks_display()
            self.save_medialist()

    def new_track(self, fields, values):
        if self.current_medialist <> None:
            #print '\nfields ', fields
            #print '\nvalues ', values
            new_track = copy.deepcopy(fields)
            #print ',\new track ',new_track
            self.current_medialist.append(new_track)
            #print '\nbefore values ',self.current_medialist.print_list()
            if values <> None:
                self.current_medialist.update(
                    self.current_medialist.length() - 1, values)
            self.current_medialist.select(self.current_medialist.length() - 1)
            self.refresh_tracks_display()
            self.save_medialist()

    def new_message_track(self):
        self.new_track(PPdefinitions.new_tracks['message'], None)

    def new_video_track(self):
        self.new_track(PPdefinitions.new_tracks['video'], None)

    def new_audio_track(self):
        self.new_track(PPdefinitions.new_tracks['audio'], None)

    def new_web_track(self):
        self.new_track(PPdefinitions.new_tracks['web'], None)

    def new_image_track(self):
        self.new_track(PPdefinitions.new_tracks['image'], None)

    def new_show_track(self):
        self.new_track(PPdefinitions.new_tracks['show'], None)

    def new_menu_background_track(self):
        self.new_track(PPdefinitions.new_tracks['menu-background'], None)

    def new_child_show_track(self):
        self.new_track(PPdefinitions.new_tracks['child-show'], None)

    def remove_track(self):
        if self.current_medialist <> None and self.current_medialist.length(
        ) > 0 and self.current_medialist.track_is_selected():
            if tkMessageBox.askokcancel("Delete Track", "Delete Track"):
                index = self.current_medialist.selected_track_index()
                self.current_medialist.remove(index)
                self.save_medialist()
                self.refresh_tracks_display()

    def add_track_from_file(self):
        if self.current_medialist == None: return
        # print "initial directory ", self.options.initial_media_dir
        files_path = tkFileDialog.askopenfilename(
            initialdir=self.options.initial_media_dir, multiple=True)
        # fix for tkinter bug
        files_path = self.root.tk.splitlist(files_path)
        for file_path in files_path:
            file_path = os.path.normpath(file_path)
            # print "file path ", file_path
            self.add_track(file_path)
        self.save_medialist()

    def add_tracks_from_dir(self):
        if self.current_medialist == None: return
        image_specs = [
            PPdefinitions.IMAGE_FILES, PPdefinitions.VIDEO_FILES,
            PPdefinitions.AUDIO_FILES, PPdefinitions.WEB_FILES,
            ('All files', '*')
        ]  #last one is ignored in finding files
        # in directory, for dialog box only
        directory = tkFileDialog.askdirectory(
            initialdir=self.options.initial_media_dir)
        # deal with tuple returned on Cancel
        if len(directory) == 0: return
        # make list of exts we recognise
        exts = []
        for image_spec in image_specs[:-1]:
            image_list = image_spec[1:]
            for ext in image_list:
                exts.append(copy.deepcopy(ext))
        for file in os.listdir(directory):
            (root_file, ext_file) = os.path.splitext(file)
            if ext_file.lower() in exts:
                file_path = directory + os.sep + file
                #print "file path before ", file_path
                file_path = os.path.normpath(file_path)
                #print "file path after ", file_path
                self.add_track(file_path)
        self.save_medialist()

    def add_track(self, afile):
        relpath = os.path.relpath(afile, self.pp_home_dir)
        #print "relative path ",relpath
        common = os.path.commonprefix([afile, self.pp_home_dir])
        #print "common ",common
        if common.endswith("pp_home") == False:
            location = afile
        else:
            location = "+" + os.sep + relpath
            location = string.replace(location, '\\', '/')
            #print "location ",location
        (root, title) = os.path.split(afile)
        (root, ext) = os.path.splitext(afile)
        if ext.lower() in PPdefinitions.IMAGE_FILES:
            self.new_track(PPdefinitions.new_tracks['image'], {
                'title': title,
                'track-ref': '',
                'location': location
            })
        elif ext.lower() in PPdefinitions.VIDEO_FILES:
            self.new_track(PPdefinitions.new_tracks['video'], {
                'title': title,
                'track-ref': '',
                'location': location
            })
        elif ext.lower() in PPdefinitions.AUDIO_FILES:
            self.new_track(PPdefinitions.new_tracks['audio'], {
                'title': title,
                'track-ref': '',
                'location': location
            })
        elif ext.lower() in PPdefinitions.WEB_FILES:
            self.new_track(PPdefinitions.new_tracks['web'], {
                'title': title,
                'track-ref': '',
                'location': location
            })
        else:
            self.mon.err(self, afile + " - file extension not recognised")

# *********************************************
# update profile
# **********************************************

    def update_all(self):
        self.init()
        for profile_file in os.listdir(self.pp_home_dir + os.sep +
                                       'pp_profiles'):
            # self.mon.log (self,"Updating "+profile_file)
            self.pp_profile_dir = self.pp_home_dir + os.sep + 'pp_profiles' + os.sep + profile_file
            if not os.path.exists(self.pp_profile_dir + os.sep +
                                  "pp_showlist.json"):
                tkMessageBox.showwarning(
                    "Pi Presents",
                    "Not a profile, skipping " + self.pp_profile_dir)
            else:
                self.current_showlist = ShowList()
                #self.mon.log (self,"Checking version "+profile_file)
                self.current_showlist.open_json(self.pp_profile_dir + os.sep +
                                                "pp_showlist.json")
                if float(self.current_showlist.sissue()) < float(
                        self.editor_issue):
                    self.mon.log(
                        self, "Version of profile " + profile_file +
                        "  is being updated to " + self.editor_issue)
                    self.update_profile()
                elif (self.command_options['forceupdate'] == True
                      and float(self.current_showlist.sissue()) == float(
                          self.editor_issue)):
                    self.mon.log(
                        self, "Forced updating of " + profile_file + ' to ' +
                        self.editor_issue)
                    self.update_profile()
                elif float(self.current_showlist.sissue()) > float(
                        self.editor_issue):
                    tkMessageBox.showwarning(
                        "Pi Presents", "Version of profile " + profile_file +
                        " is greater than editor, skipping")
                else:
                    self.mon.log(
                        self,
                        " Profile " + profile_file + " Already up to date ")
        self.init()
        tkMessageBox.showwarning("Pi Presents", "All profiles updated")

    def update_profile(self):
        #open showlist and update its shows
        # self.mon.log (self,"Updating show ")
        ifile = open(self.pp_profile_dir + os.sep + "pp_showlist.json", 'rb')
        shows = json.load(ifile)['shows']
        ifile.close()
        replacement_shows = self.update_shows(shows)
        dic = {'issue': self.editor_issue, 'shows': replacement_shows}
        ofile = open(self.pp_profile_dir + os.sep + "pp_showlist.json", "wb")
        json.dump(dic, ofile, sort_keys=True, indent=1)

        # UPDATE MEDIALISTS AND THEIR TRACKS
        for file in os.listdir(self.pp_profile_dir):
            if file.endswith(".json") and file <> 'pp_showlist.json':
                # self.mon.log (self,"Updating medialist " + file)
                #open a medialist and update its tracks
                ifile = open(self.pp_profile_dir + os.sep + file, 'rb')
                tracks = json.load(ifile)['tracks']
                ifile.close()
                replacement_tracks = self.update_tracks(tracks)
                dic = {
                    'issue': self.editor_issue,
                    'tracks': replacement_tracks
                }
                ofile = open(self.pp_profile_dir + os.sep + file, "wb")
                json.dump(dic, ofile, sort_keys=True, indent=1)

    def update_tracks(self, old_tracks):
        # get correct spec from type of field
        replacement_tracks = []
        for old_track in old_tracks:
            #print '\nold track ',old_track
            track_type = old_track['type']
            spec_fields = PPdefinitions.new_tracks[track_type]
            left_overs = dict()
            # go through track and delete fields not in spec
            for key in old_track.keys():
                if key in spec_fields:
                    left_overs[key] = old_track[key]
            #print '\n leftovers',left_overs
            replacement_track = copy.deepcopy(
                PPdefinitions.new_tracks[track_type])
            #print '\n before update', replacement_track
            replacement_track.update(left_overs)
            #print '\nafter update',replacement_track
            replacement_tracks.append(copy.deepcopy(replacement_track))
        return replacement_tracks

    def update_shows(self, old_shows):
        # get correct spec from type of field
        replacement_shows = []
        for old_show in old_shows:
            show_type = old_show['type']
            spec_fields = PPdefinitions.new_shows[show_type]
            left_overs = dict()
            # go through track and delete fields not in spec
            for key in old_show.keys():
                if key in spec_fields:
                    left_overs[key] = old_show[key]
            # print '\n leftovers',left_overs
            replacement_show = copy.deepcopy(
                PPdefinitions.new_shows[show_type])
            replacement_show.update(left_overs)
            replacement_shows.append(copy.deepcopy(replacement_show))
        return replacement_shows
Example #31
0
class IOPluginManager(object):

    plugins = []

    def __init__(self):
        self.mon = Monitor()

    def init(self, pp_dir, pp_profile, widget, callback, pp_home):
        self.pp_dir = pp_dir
        self.pp_profile = pp_profile
        self.pp_home = pp_home
        IOPluginManager.plugins = []

        if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'):
            # read the .cfg files in /pp_io_config in profile registring the I/O plugin
            for cfgfile in os.listdir(self.pp_profile + os.sep +
                                      'pp_io_config'):
                if cfgfile in ('screen.cfg', 'osc.cfg'):
                    continue
                cfgfilepath = self.pp_profile + os.sep + 'pp_io_config' + os.sep + cfgfile
                status, message = self.init_config(cfgfile, cfgfilepath,
                                                   widget, callback)
                if status == 'error':
                    return status, message

        #read .cfg file in /pipresents/pp_io_config if file not present in profile then use this one
        for cfgfile in os.listdir(self.pp_dir + os.sep + 'pp_io_config'):
            if cfgfile in ('screen.cfg', 'osc.cfg'):
                continue
            if not os.path.exists(self.pp_profile + os.sep + 'pp_io_config' +
                                  os.sep + cfgfile):
                cfgfilepath = self.pp_dir + os.sep + 'pp_io_config' + os.sep + cfgfile
                status, message = self.init_config(cfgfile, cfgfilepath,
                                                   widget, callback)
                if status == 'error':
                    return status, message

        # print IOPluginManager.plugins
        return 'normal', 'I/O Plugins registered'

    def init_config(self, cfgfile, cfgfilepath, widget, callback):
        # print cfgfile,cfgfilepath
        reason, message, config = self._read(cfgfile, cfgfilepath)
        if reason == 'error':
            self.mon.err(self, 'Failed to read ' + cfgfile + ' ' + message)
            return 'error', 'Failed to read ' + cfgfile + ' ' + message
        if config.has_section('DRIVER') is False:
            self.mon.err(self, 'No DRIVER section in ' + cfgfilepath)
            return 'error', 'No DRIVER section in ' + cfgfilepath
        entry = dict()
        #read information from DRIVER section
        entry['title'] = config.get('DRIVER', 'title')
        if config.get('DRIVER', 'enabled') == 'yes':
            if config.has_option('DRIVER', 'driver-ref'):
                entry['driver-ref'] = config.get('DRIVER', 'driver-ref')
            else:
                entry['driver-ref'] = ''
            driver_name = config.get('DRIVER', 'module')
            driver_path = self.pp_dir + os.sep + 'pp_io_plugins' + os.sep + driver_name + '.py'
            if not os.path.exists(driver_path):
                self.mon.err(
                    self, driver_name + ' Driver not found in ' + driver_path)
                return 'error', driver_name + ' Driver not found in ' + driver_path

            instance = self._load_plugin_file(
                driver_name, self.pp_dir + os.sep + 'pp_io_plugins')
            reason, message = instance.init(cfgfile, cfgfilepath, widget,
                                            self.pp_dir, self.pp_home,
                                            self.pp_profile, callback)
            if reason == 'warn':
                self.mon.warn(self, message)
                return 'error', message
            if reason == 'error':
                self.mon.warn(self, message)
                return 'error', message
            entry['instance'] = instance
            self.mon.log(self, message)
            IOPluginManager.plugins.append(entry)
        return 'normal', 'I/O Plugins registered'

    def start(self):
        for entry in IOPluginManager.plugins:
            plugin = entry['instance']
            if plugin.is_active() is True:
                plugin.start()

    def terminate(self):
        for entry in IOPluginManager.plugins:
            plugin = entry['instance']
            if plugin.is_active() is True:
                plugin.terminate()
                self.mon.log(self,
                             'I/O plugin ' + entry['title'] + ' terminated')

    def get_input(self, key, driver_ref=''):
        for entry in IOPluginManager.plugins:
            plugin = entry['instance']
            # print ('trying ',entry['title'],plugin.is_active())
            if plugin.is_active(
            ) is True and driver_ref == entry['driver-ref']:
                # need to test found in plugin to allow key to match if driver-ref not used
                found, value = plugin.get_input(key)
                if found is True:
                    return found, value
        # key not found in any plugin
        return False, None

    def handle_output_event(self, name, param_type, param_values, req_time):
        for entry in IOPluginManager.plugins:
            plugin = entry['instance']
            # print ('trying ',entry['title'],name,param_type,plugin.is_active())
            if plugin.is_active() is True:
                # print (name,param_type,param_values,req_time)
                reason, message = plugin.handle_output_event(
                    name, param_type, param_values, req_time)
                if reason == 'error':
                    # self.mon.err(self,message)
                    return 'error', message
                else:
                    self.mon.log(self, message)
        return 'normal', 'output scan complete'

    def _load_plugin_file(self, name, driver_dir):
        fp, pathname, description = imp.find_module(name, [driver_dir])
        module_id = imp.load_module(name, fp, pathname, description)
        plugin_class = getattr(module_id, name)
        return plugin_class()

    def _read(self, filename, filepath):
        if os.path.exists(filepath):
            config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
            config.read(filepath)
            self.mon.log(self, filename + " read from " + filepath)
            return 'normal', filename + ' read', config
        else:
            return 'error', filename + ' not found at: ' + filepath, None
Example #32
0
class ImagePlayer:
    """ Displays an image on a canvas for a period of time. Image display can be interrupted
          Implements animation of transitions but Pi is too slow without GPU aceleration."""

    # slide state constants
    NO_SLIDE = 0
    SLIDE_IN = 1
    SLIDE_DWELL = 2
    SLIDE_OUT = 3

    # *******************
    # external commands
    # *******************

    def __init__(self, canvas, cd, track_params):
        """
                canvas - the canvas onto which the image is to be drawn
                cd -  dictionary of show parameters
                track_params - disctionary of track paramters
        """

        self.mon = Monitor()
        self.mon.on()

        self.canvas = canvas
        self.cd = cd
        self.track_params = track_params

        # open resources
        self.rr = ResourceReader()

        # get config from medialist if there.
        if 'duration' in self.track_params and self.track_params[
                'duration'] <> "":
            self.duration = int(self.track_params['duration'])
        else:
            self.duration = int(self.cd['duration'])

        if 'transition' in self.track_params and self.track_params[
                'transition'] <> "":
            self.transition = self.track_params['transition']
        else:
            self.transition = self.cd['transition']

        # keep dwell and porch as an integer multiple of tick
        self.porch = 1000  #length of pre and post porches for an image (milliseconds)
        self.tick = 100  # tick time for image display (milliseconds)
        self.dwell = (1000 * self.duration) - (2 * self.porch)
        if self.dwell < 0: self.dwell = 0

        self.centre_x = int(self.canvas['width']) / 2
        self.centre_y = int(self.canvas['height']) / 2

    def play(self,
             track,
             end_callback,
             ready_callback,
             enable_menu=False,
             starting_callback=None,
             playing_callback=None,
             ending_callback=None):

        # instantiate arguments
        self.track = track
        self.enable_menu = enable_menu
        self.ready_callback = ready_callback
        self.end_callback = end_callback

        #init state and signals
        self.state = ImagePlayer.NO_SLIDE
        self.quit_signal = False
        self.kill_required_signal = False
        self.error = False
        self._tick_timer = None
        self.drawn = None
        self.paused = False
        self.pause_text = None

        if os.path.exists(self.track) == True:
            self.pil_image = PIL.Image.open(self.track)
            # adjust brightness and rotate (experimental)
            # pil_image_enhancer=PIL.ImageEnhance.Brightness(pil_image)
            # pil_image=pil_image_enhancer.enhance(0.1)
            # pil_image=pil_image.rotate(45)
            # tk_image = PIL.ImageTk.PhotoImage(pil_image)
        else:
            self.pil_image = None
            # display 'Out of Order' for 7 seconds
            self.dwell = (1000 * 7) - (2 * self.porch)
            if self.dwell < 0: self.dwell = 0

        # and start image rendering
        self._start_front_porch()

    def key_pressed(self, key_name):
        if key_name == '':
            return
        elif key_name in ('p', ' '):
            self.pause()
        elif key_name == 'escape':
            self._stop()
            return

    def button_pressed(self, button, edge):
        if button == 'pause':
            self.pause()
        elif button == 'stop':
            self._stop()
            return

    def terminate(self, reason):
        if reason == 'error':
            self.error = True
            self.quit_signal = True
        else:
            self.kill_required_signal = True
            self.quit_signal = True

    def pause(self):
        if not self.paused:
            self.paused = True
        else:
            self.paused = False

# *******************
# internal functions
# *******************

    def _stop(self):
        self.quit_signal = True

    def _error(self):
        self.error = True
        self.quit_signal = True

    #called when back porch has completed or quit signal is received
    def _end(self, reason, message):
        if self._tick_timer <> None:
            self.canvas.after_cancel(self._tick_timer)
            self._tick_timer = None
        self.quit_signal = False
        # self.canvas.delete(ALL)
        self.canvas.update_idletasks()
        self.state = self.NO_SLIDE
        if self.error == True:
            self.end_callback("error", message)
            self = None
        elif self.kill_required_signal == True:
            self.end_callback("killed", message)
            self = None
        else:
            self.end_callback(reason, message)
            self = None

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self._error()
        else:
            return value

    def _start_front_porch(self):
        self.state = ImagePlayer.SLIDE_IN
        self.porch_counter = 0
        if self.ready_callback <> None: self.ready_callback()

        if self.transition == "cut":
            #just display the slide full brightness. No need for porch but used for symmetry
            if self.pil_image <> None:
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        elif self.transition == "fade":
            #experimental start black and increase brightness (controlled by porch_counter).
            self._display_image()

        elif self.transition == "slide":
            #experimental, start in middle and move to right (controlled by porch_counter)
            if self.pil_image <> None:
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        elif self.transition == "crop":
            #experimental, start in middle and crop from right (controlled by porch_counter)
            if self.pil_image <> None:
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        self._tick_timer = self.canvas.after(self.tick, self._do_front_porch)

    def _do_front_porch(self):
        if self.quit_signal == True:
            self._end('normal', 'user quit')
        else:
            self.porch_counter = self.porch_counter + 1
            # print "doing slide front porch " +str(self.porch_counter)
            self._display_image()
            if self.porch_counter == self.porch / self.tick:
                self._start_dwell()
            else:
                self._tick_timer = self.canvas.after(self.tick,
                                                     self._do_front_porch)

    def _start_dwell(self):
        self.state = ImagePlayer.SLIDE_DWELL
        self.dwell_counter = 0
        self._tick_timer = self.canvas.after(self.tick, self._do_dwell)

    def _do_dwell(self):
        if self.quit_signal == True:
            self.mon.log(self, "quit received")
            self._end('normal', 'user quit')
        else:
            if self.paused == False:
                self.dwell_counter = self.dwell_counter + 1

            # one time flipping of pause text
            if self.paused == True and self.pause_text == None:
                self.pause_text = self.canvas.create_text(100,
                                                          100,
                                                          anchor=NW,
                                                          text=self.resource(
                                                              'imageplayer',
                                                              'm01'),
                                                          fill="white",
                                                          font="arial 25 bold")
                self.canvas.update_idletasks()

            if self.paused == False and self.pause_text <> None:
                self.canvas.delete(self.pause_text)
                self.pause_text = None
                self.canvas.update_idletasks()

            if self.dwell_counter == self.dwell / self.tick:
                self._start_back_porch()
            else:
                self._tick_timer = self.canvas.after(self.tick, self._do_dwell)

    def _start_back_porch(self):
        self.state = ImagePlayer.SLIDE_OUT
        self.porch_counter = self.porch / self.tick

        if self.transition == "cut":
            # just keep displaying the slide full brightness.
            # No need for porch but used for symmetry
            pass

        elif self.transition == "fade":
            #experimental start full and decrease brightness (controlled by porch_counter).
            self._display_image()

        elif self.transition == "slide":
            #experimental, start in middle and move to right (controlled by porch_counter)
            if self.pil_image <> None:
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        elif self.transition == "crop":
            #experimental, start in middle and crop from right (controlled by porch_counter)
            if self.pil_image <> None:
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_image)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        self._tick_timer = self.canvas.after(self.tick, self._do_back_porch)

    def _do_back_porch(self):
        if self.quit_signal == True:
            self._end('normal', 'user quit')
        else:
            self.porch_counter = self.porch_counter - 1
            self._display_image()
            if self.porch_counter == 0:
                self._end('normal', 'finished')
            else:
                self._tick_timer = self.canvas.after(self.tick,
                                                     self._do_back_porch)

    def _display_image(self):
        if self.transition == "cut":
            pass

        # all the methods below have incorrect code !!!
        elif self.transition == "fade":
            if self.pil_image <> None:
                self.enh = PIL.ImageEnhance.Brightness(self.pil_image)
                prop = float(self.porch_counter) / float(20)  #????????
                self.pil_img = self.enh.enhance(prop)
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_img)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        elif self.transition == "slide":
            if self.pil_image <> None:
                self.canvas.move(self.drawn, 5, 0)

        elif self.transition == "crop":
            if self.pil_image <> None:
                self.crop = 10 * self.porch_counter
                self.pil_img = self.pil_image.crop(
                    (0, 0, 1000 - self.crop, 1080))
                self.tk_img = PIL.ImageTk.PhotoImage(self.pil_img)
                self.drawn = self.canvas.create_image(self.centre_x,
                                                      self.centre_y,
                                                      image=self.tk_img,
                                                      anchor=CENTER)

        # display message if image is not available

        if self.pil_image == None:
            self.canvas.create_text(self.centre_x,
                                    self.centre_y,
                                    text=self.resource('imageplayer', 'm02'),
                                    fill='white',
                                    font='arial 30 bold')

        # display instructions if enabled

        if self.enable_menu == True:
            self.canvas.create_text(self.centre_x,
                                    int(self.canvas['height']) -
                                    int(self.cd['hint-y']),
                                    text=self.cd['hint-text'],
                                    fill=self.cd['hint-colour'],
                                    font=self.cd['hint-font'])

        # display show text if enabled
        if self.cd['show-text'] <> '':
            self.canvas.create_text(int(self.cd['show-text-x']),
                                    int(self.cd['show-text-y']),
                                    anchor=NW,
                                    text=self.cd['show-text'],
                                    fill=self.cd['show-text-colour'],
                                    font=self.cd['show-text-font'])

        # display track text if enabled
        if self.track_params['track-text'] <> '':
            self.canvas.create_text(
                int(self.track_params['track-text-x']),
                int(self.track_params['track-text-y']),
                anchor=NW,
                text=self.track_params['track-text'],
                fill=self.track_params['track-text-colour'],
                font=self.track_params['track-text-font'])

        self.canvas.update_idletasks()
class ControlsManager:
    config = None
    global_controls = []

    def __init__(self):
        self.mon = Monitor()
        self.mon.on()

    #read controls.cfg, done once in Pi Presents
    def read(self, pp_dir, pp_home, pp_profile):
        if ControlsManager.config == None:
            # try inside profile
            tryfile = pp_profile + os.sep + "controls.cfg"
            # self.mon.log(self,"Trying controls.cfg in profile at: "+ tryfile)
            if os.path.exists(tryfile):
                filename = tryfile
            else:
                # try inside pp_home
                # self.mon.log(self,"controls.cfg not found at "+ tryfile+ " trying pp_home")
                tryfile = pp_home + os.sep + "controls.cfg"
                if os.path.exists(tryfile):
                    filename = tryfile
                else:
                    # try inside pipresents
                    # self.mon.log(self,"controls.cfg not found at "+ tryfile + " trying inside pipresents")
                    tryfile = pp_dir + os.sep + 'pp_home' + os.sep + "controls.cfg"
                    if os.path.exists(tryfile):
                        filename = tryfile
                    else:
                        self.mon.log(self,
                                     "controls.cfg not found at " + tryfile)
                        self.mon.err(self, "controls.cfg not found")
                        return False
            ControlsManager.config = ConfigParser.ConfigParser()
            ControlsManager.config.read(filename)
            self.mon.log(self, "controls.cfg read from " + filename)
            return True

    def parse_defaults(self):
        if ControlsManager.config.has_section('controls'):
            ControlsManager.global_controls = ControlsManager.config.items(
                'controls')
            # print 'global controls ',ControlsManager.global_controls
            return True
        else:
            return False

    # get the default controls for the show that has been read in by read from controls.cfg
    def default_controls(self):
        control_defs = ControlsManager.global_controls
        controls_list = []
        for control_def in control_defs:
            op = control_def[1]
            default_name = control_def[0]
            controls_list.append([default_name, op])
        return controls_list

    # Merge in controls from a show.

    # the set of default controls for all shows can be overridden in a top level show
    # by the controls defined in the show
    # if show has an operation other than 'null' change the symbolic name to that inthe show
    # if the show has a null operation change the operation of the attached symbolic name to null

    def merge_show_controls(self, controls_list, show_text):
        # print 'show text: ',show_text
        reason, message, show_controls = self.parse_controls(show_text)
        # print 'show controls:',show_controls

        # overwrite the default symbolic_name if re-defined in the show
        for show_control in show_controls:
            show_name = show_control[0]
            show_operation = show_control[1]
            # find the operation in the controls list and change its name
            # print 'op to change name of: ',show_operation
            # print 'change to ',show_name
            for control in controls_list:
                if control[1] == show_operation:
                    control[0] = show_name
        # print 'after rename ',controls_list

        # add additional operations if defined in the show
        for show_control in show_controls:
            show_name = show_control[0]
            show_operation = show_control[1]
            # is operation in the controls list
            # print 'op to add: ',show_operation
            # print 'name to add ',show_name
            found = False
            for control in controls_list:
                if control[1] == show_operation:
                    found = True

            # if the operation has not been found and it is omx- or mplay- then add it.
            if found == False and (show_operation[0:4] == 'omx-'
                                   or show_operation[0:6] == 'mplay-'):
                #  print 'appending ', show_name,show_operation
                controls_list.append([show_name, show_operation])

        # step through controls list dealing with null
        new_controls = []
        # find the name in controls list and delete it?
        for control in controls_list:
            name = control[0]
            operation = control[1]
            found = False
            for show_control in show_controls:
                show_name = show_control[0]
                show_operation = show_control[1]
                if show_name == name and show_operation == 'null':
                    found = True
                    break
            if found == False:
                new_controls.append(control)
                #  print 'preserved ',control

        # print 'merged controls',new_controls
        return new_controls

    #  parse controls from controls field in a show
    def parse_controls(self, controls_text):
        controls = []
        lines = controls_text.split('\n')
        num_lines = 0
        for line in lines:
            if line.strip() == "":
                continue
            num_lines += 1
            error_text, control = self.parse_control(line.strip())
            if error_text <> "":
                return 'error', error_text, controls
            controls.append(copy.deepcopy(control))
        return 'normal', '', controls

    def parse_control(self, line):
        fields = line.split()
        if len(fields) <> 2:
            return "incorrect number of fields in control", ['', '']
        symbol = fields[0]
        operation = fields[1]
        if operation in (
                'stop', 'play', 'up', 'down', 'pause', 'null'
        ) or operation[0:4] == 'omx-' or operation[0:6] == 'mplay-':
            return '', [symbol, operation]
        else:
            return "unknown operation", ['', '']
Example #34
0
class PiPresents:
    def __init__(self):
        
        self.pipresents_issue="1.1"
        
        StopWatch.global_enable=False

#****************************************
# INTERPRET COMMAND LINE
# ***************************************

        self.options=command_options()
        

        pp_dir=sys.path[0]
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()

        
        #Initialise logging
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        self.mon.on()
        if self.options['debug']==True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False
 
        self.mon.log (self, "Pi Presents is starting")
        
        #self.show=None
        
        # create  profile  for pp_editor test files if already not there.
        if not os.path.exists(pp_dir+"/pp_home/pp_profiles/pp_editor"):
            self.mon.log(self,"Making pp_editor directory") 
            os.makedirs(pp_dir+"/pp_home/pp_profiles/pp_editor")
            
            
        #profile path from -p option
        if self.options['profile']<>"":
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.pp_profile_path = "/pp_profiles/pp_profile"
        
       #get directory containing pp_home from the command,
        if self.options['home'] =="":
            home = os.path.expanduser('~')+ os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"
                   
        #check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        # fall back to pipresents/pp_home
        self.pp_home=pp_dir+"/pp_home"
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " " + str(i))
            if os.path.exists(home):
                self.mon.log(self,"Using pp_home at: " + home)
                self.pp_home=home
                break
            time.sleep (1)

        #check profile exists, if not default to error profile inside pipresents
        self.pp_profile=self.pp_home+self.pp_profile_path
        if not os.path.exists(self.pp_profile):
            self.pp_profile=pp_dir+"/pp_home/pp_profiles/pp_profile"

        if self.options['verify']==True:
            val =Validator()
            if  val.validate_profile(None,self.pp_home,self.pp_profile,self.pipresents_issue,False) == False:
                tkMessageBox.showwarning("Pi Presents","Validation Failed")
                exit()
                
        # open the resources
        self.rr=ResourceReader()
        # read the file, done once for all the other classes to use.
        self.rr.read(pp_dir,self.pp_home)

        
        #initialise the showlists and read the showlists
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self._end('error','showlist not found')

        if float(self.showlist.sissue())<>float(self.pipresents_issue):
            self.mon.err(self,"Version of profile " + self.showlist.sissue() + " is not  same as Pi Presents, must exit")
            self._end('error','wrong version of profile')
 
        # get the starter show from the showlist
        index = self.showlist.index_of_show('start')
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self._end('error','start show not found')
            
# ********************
# SET UP THE GUI
# ********************

        #turn off the screenblanking and saver
        if self.options['noblank']==True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        # control display of window decorations
        if self.options['fullscreen']<>"partial":
            self.root = Tk(className="fspipresents")
            os.system('unclutter &')
        else:
              self.root = Tk(className="pipresents")          


        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg='black')
        
        # get size of the screen
        self.screen_width = self.root.winfo_screenwidth()
        self.screen_height = self.root.winfo_screenheight()

        # set window dimensions
        self.window_height=self.screen_height
        self.window_width=self.screen_width
        self.window_x=0
        self.window_y=0
        if self.options['fullscreen']<>"partial":
            bar=self.options['fullscreen']
            # allow just 2 pixels for the hidden taskbar
            if bar in ('left','right'):
                self.window_width=self.screen_width-2
            else:
                self.window_height=self.screen_height-2
            if bar =="left":
                self.window_x=2
            if bar =="top":
                self.window_y=2   
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')
        else:
            self.window_width=self.screen_width-200
            self.window_height=self.screen_height-200
            self.window_x=50
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
            


        
        #canvas covers the whole window
        self.canvas_height=self.window_height
        self.canvas_width=self.window_width
        
        # make sure focus is set.
        self.root.focus_set()

        #define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.on_break_key)

        # Always use CTRL-Break key to close the program as a get out of jail
        self.root.bind("<Break>",self.e_on_break_key)
        
        #pass all other keys along to 'shows' and hence to 'players'
        self.root.bind("<Escape>", self._escape_pressed)
        self.root.bind("<Up>", self._up_pressed)
        self.root.bind("<Down>", self._down_pressed)
        self.root.bind("<Return>", self._return_pressed)
        self.root.bind("<space>", self._pause_pressed)
        self.root.bind("p", self._pause_pressed)

        #setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg='black')

        self.canvas.config(height=self.canvas_height, width=self.canvas_width)
        #self.canvas.grid(row=1,columnspan=2)
        self.canvas.pack()
        # make sure focus is set on canvas.
        self.canvas.focus_set()


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        
        #kick off GPIO if enabled by command line option
        if self.options['gpio']==True:
            from pp_buttons import Buttons
            # initialise the buttons connected to GPIO
            self.Buttons=Buttons
            self.buttons = Buttons(self.root,20,self.button_pressed)
            self.buttons.poll()

            
        #  kick off the initial show            
        self.show=None
        # get the start show from the showlist
        index = self.showlist.index_of_show(self.starter_show['start-show'])
        if index >=0:
            self.showlist.select(index)
            self.start_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show not found in showlist: "+ self.starter_show['start-show'])
            self._end('error','show not found in showlist')
            
        if self.start_show['type']=="mediashow":
            self.show= MediaShow(self.start_show,
                                                            self.canvas,
                                                            self.showlist,
                                                            self.pp_home,
                                                            self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )     
            
        elif self.start_show['type']=="menu":
            self.show= MenuShow(self.start_show,
                                                    self.canvas,
                                                    self.showlist,
                                                    self.pp_home,
                                                    self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )

        elif self.start_show['type']=="liveshow":
            self.show= LiveShow(self.start_show,
                                                    self.canvas,
                                                    self.showlist,
                                                    self.pp_home,
                                                    self.pp_profile)
            self.show.play(self._end_play_show,top=True,command='nil')
            self.root.mainloop( )                 
            
        else:
            self.mon.err(self,"unknown mediashow type in start show - "+ self.start_show['type'])
            self._end('error','unknown mediashow type')

    def _end_play_show(self,reason,message):
        self.mon.log(self,"Returned to piresents with reason: " + reason +" and message " + message)
        self._end(reason,message)
     
    def _end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with message: " + message)
        self.show=None
        if reason=='error':
            self.mon.log(self, "exiting because of error")
            self.tidy_up()
            exit()            
        if reason=='killed':
            self.mon.log(self,"kill received - exiting")
            self.on_kill_callback()
        else:
            # should never be here or fatal error
            self.mon.log(self, "exiting because invalid end reasosn")
            self.tidy_up()
            exit()

             

# *********************
# EXIT APP
# *********************

    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.mon.log(self,"sent terminate to shower")
            self.shower.terminate(reason)
        else:
            self._end(reason,message)



    def tidy_up(self):
        #turn screen blanking back on
        if self.options['noblank']==True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
        if self.options['gpio']==True:
            self.buttons.kill()
        #close logging files 
        self.mon.finish()

    def on_kill_callback(self):
        self.tidy_up()
        if self.shutdown_required==True:
            call(['sudo', 'shutdown', '-h', '-t 5','now'])
        else:
            exit()


    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value

# *********************
# Key and button presses
# ********************

    def shutdown_pressed(self):
        self.root.after(5000,self.on_shutdown_delay)

    def on_shutdown_delay(self):
        if self.buttons.is_pressed(self.Buttons.SHUTDOWN):
            self.shutdown_required=True
            self.on_break_key()

    def button_pressed(self,index,button,edge):
        self.mon.log(self, "Button Pressed: "+button)
        if button=="shutdown":
            self.shutdown_pressed()
        else:
            if self.show<>None:
                self.show.button_pressed(button,edge)
  
    # key presses
    def _escape_pressed(self,event): self._key_pressed("escape")              
    def _up_pressed(self,event): self._key_pressed("up")  
    def _down_pressed(self,event): self._key_pressed("down")  
    def _return_pressed(self,event): self._key_pressed("return")
    def _pause_pressed(self,event): self._key_pressed("p")
        

    def _key_pressed(self,key_name):
        self.mon.log(self, "Key Pressed: "+ key_name)
        # if a show is running pass the key to it.
        if self.show<>None:
            self.show.key_pressed(key_name)
         
    def on_break_key(self):
        self.mon.log(self, "kill received from user")
        #terminate any running shows and players     
        if self.show<>None:
            self.mon.log(self,"kill sent to show")   
            self.show.terminate('killed')
    
    def e_on_break_key(self,event):
        self.on_break_key()
Example #35
0
class Player(object):

    # common bits of __init__(...)
    def __init__(self, show_id, showlist, root, canvas, show_params,
                 track_params, pp_dir, pp_home, pp_profile, end_callback,
                 command_callback):

        # create debugging log object
        self.mon = Monitor()

        self.mon.trace(self, '')

        # instantiate arguments
        self.show_id = show_id
        self.showlist = showlist
        self.root = root
        self.canvas = canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height = canvas['show-canvas-height']
        self.show_canvas_centre_x = canvas['show-canvas-centre-x']
        self.show_canvas_centre_y = canvas['show-canvas-centre-y']
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.end_callback = end_callback
        self.command_callback = command_callback

        # get background image from profile.
        self.background_file = ''
        if self.track_params['background-image'] != '':
            self.background_file = self.track_params['background-image']

        # get background colour from profile.
        if self.track_params['background-colour'] != '':
            self.background_colour = self.track_params['background-colour']
        else:
            self.background_colour = self.show_params['background-colour']

        # get animation instructions from profile
        self.animate_begin_text = self.track_params['animate-begin']
        self.animate_end_text = self.track_params['animate-end']

        # create an  instance of showmanager so we can control concurrent shows
        # self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        # open the plugin Manager
        self.pim = PluginManager(self.show_id, self.root, self.canvas,
                                 self.show_params, self.track_params,
                                 self.pp_dir, self.pp_home, self.pp_profile)

        # create an instance of Animate so we can send animation commands
        self.animate = Animate()

        # initialise state and signals
        self.background_obj = None
        self.show_text_obj = None
        self.track_text_obj = None
        self.hint_obj = None
        self.background = None
        self.freeze_at_end_required = 'no'  # overriden by videoplayer
        self.tick_timer = None
        self.terminate_signal = False
        self.play_state = ''

    def pre_load(self):
        # Control other shows at beginning
        self.show_control(self.track_params['show-control-begin'])
        pass

    # common bits of show(....)
    def pre_show(self):
        self.mon.trace(self, '')

        # show_x_content moved to just before ready_callback to improve flicker.
        self.show_x_content()

        # and whatecer the plugin has created
        self.pim.show_plugin()

        #ready callback hides and closes players from previous track, also displays show background
        if self.ready_callback is not None:
            self.ready_callback(self.enable_show_background)

        # create animation events
        reason, message = self.animate.animate(self.animate_begin_text,
                                               id(self))
        if reason == 'error':
            self.mon.err(self, message)
            self.play_state = 'show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error', message)
        else:
            # return to start playing the track.
            self.mon.log(
                self,
                ">show track received from show Id: " + str(self.show_id))
            return

    # to keep landscape happy
    def ready_callback(self, enable_show_background):
        self.mon.fatal(self, 'ready callback not overridden')
        self.end('error', 'ready callback not overridden')

    def finished_callback(self, reason, message):
        self.mon.fatal(self, 'finished callback not overridden')
        self.end('error', 'finished callback not overridden')

    def closed_callback(self, reason, message):
        self.mon.fatal(self, 'closed callback not overridden')
        self.end('error', 'closed callback not overridden')

# Control shows so pass the show control commands back to PiPresents via the command callback

    def show_control(self, show_control_text):
        lines = show_control_text.split('\n')
        for line in lines:
            if line.strip() == "":
                continue
            # print 'show control command: ',line
            self.command_callback(line,
                                  source='track',
                                  show=self.show_params['show-ref'])

# *****************
# hide content and end animation, show control etc.
# called by ready calback and end
# *****************

    def hide(self):
        self.mon.trace(self, '')
        # abort the timer
        if self.tick_timer is not None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        self.hide_x_content()

        # stop the plugin
        if self.track_params['plugin'] != '':
            self.pim.stop_plugin()

        # Control concurrent shows at end
        self.show_control(self.track_params['show-control-end'])

        # clear events list for this track
        if self.track_params['animate-clear'] == 'yes':
            self.animate.clear_events_list(id(self))

        # create animation events for ending
        reason, message = self.animate.animate(self.animate_end_text, id(self))
        if reason == 'error':
            self.play_state = 'show-failed'
            if self.finished_callback is not None:
                self.finished_callback('error', message)
        else:
            return

    def terminate(self):
        self.mon.trace(self, '')
        self.terminate_signal = True
        if self.play_state == 'showing':
            # call the derived class's stop method
            self.stop()
        else:
            self.end('killed', 'terminate with no track or show open')

    # must be overriden by derived class
    def stop(self):
        self.mon.fatal(self, 'stop not overidden by derived class')
        self.play_state = 'show-failed'
        if self.finished_callback is not None:
            self.finished_callback('error',
                                   'stop not overidden by derived class')

    def get_play_state(self):
        return self.play_state

# *****************
# ending the player
# *****************

    def end(self, reason, message):
        self.mon.trace(self, '')
        # stop the plugin

        if self.terminate_signal is True:
            reason = 'killed'
            self.terminate_signal = False
            self.hide()

        self.end_callback(reason, message)
        self = None

# *****************
# displaying common things
# *****************

    def load_plugin(self):
        # load the plugin if required
        if self.track_params['plugin'] != '':
            reason, message, self.track = self.pim.load_plugin(
                self.track, self.track_params['plugin'])
            return reason, message

    def draw_plugin(self):
        # load the plugin if required
        if self.track_params['plugin'] != '':
            self.pim.draw_plugin()
            return

    def load_x_content(self, enable_menu):
        self.mon.trace(self, '')
        self.background_obj = None
        self.background = None
        self.track_text_obj = None
        self.show_text_obj = None
        self.hint_obj = None
        self.track_obj = None

        # background image
        if self.background_file != '':
            background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error', "Track background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize(
                        (window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1,
                    self.show_canvas_y1,
                    image=self.background,
                    anchor=NW)
                # print '\nloaded background_obj: ',self.background_obj

        # load the track content.  Dummy function below is overridden in players
        status, message = self.load_track_content()
        if status == 'error':
            return 'error', message

        # load show text if enabled
        if self.show_params['show-text'] != '' and self.track_params[
                'display-show-text'] == 'yes':

            x, y, anchor, justify = calculate_text_position(
                self.show_params['show-text-x'],
                self.show_params['show-text-y'], self.show_canvas_x1,
                self.show_canvas_y1, self.show_canvas_centre_x,
                self.show_canvas_centre_y, self.show_canvas_x2,
                self.show_canvas_y2, self.show_params['show-text-justify'])

            self.show_text_obj = self.canvas.create_text(
                x,
                y,
                anchor=anchor,
                justify=justify,
                text=self.show_params['show-text'],
                fill=self.show_params['show-text-colour'],
                font=self.show_params['show-text-font'])

        # load track text if enabled
        if self.track_params['track-text'] != '':

            x, y, anchor, justify = calculate_text_position(
                self.track_params['track-text-x'],
                self.track_params['track-text-y'], self.show_canvas_x1,
                self.show_canvas_y1, self.show_canvas_centre_x,
                self.show_canvas_centre_y, self.show_canvas_x2,
                self.show_canvas_y2, self.track_params['track-text-justify'])

            self.track_text_obj = self.canvas.create_text(
                x,
                y,
                anchor=anchor,
                justify=justify,
                text=self.track_params['track-text'],
                fill=self.track_params['track-text-colour'],
                font=self.track_params['track-text-font'])

        # load instructions if enabled
        if enable_menu is True:

            x, y, anchor, justify = calculate_text_position(
                self.show_params['hint-x'], self.show_params['hint-y'],
                self.show_canvas_x1, self.show_canvas_y1,
                self.show_canvas_centre_x, self.show_canvas_centre_y,
                self.show_canvas_x2, self.show_canvas_y2,
                self.show_params['hint-justify'])

            self.hint_obj = self.canvas.create_text(
                x,
                y,
                justify=justify,
                text=self.show_params['hint-text'],
                fill=self.show_params['hint-colour'],
                font=self.show_params['hint-font'],
                anchor=anchor)

        self.display_show_canvas_rectangle()

        self.pim.draw_plugin()

        self.canvas.tag_raise('pp-click-area')
        self.canvas.itemconfig(self.background_obj, state='hidden')
        self.canvas.itemconfig(self.show_text_obj, state='hidden')
        self.canvas.itemconfig(self.track_text_obj, state='hidden')
        self.canvas.itemconfig(self.hint_obj, state='hidden')
        self.canvas.update_idletasks()
        return 'normal', 'x-content loaded'

    # display the rectangle that is the show canvas
    def display_show_canvas_rectangle(self):
        # coords=[self.show_canvas_x1,self.show_canvas_y1,self.show_canvas_x2-1,self.show_canvas_y2-1]
        # self.canvas.create_rectangle(coords,
        #            outline='yellow',
        #          fill='')
        pass

    # dummy functions to manipulate the track content, overidden in some players,
    # message text in messageplayer
    # image in imageplayer
    # menu stuff in menuplayer

    def load_track_content(self):
        return 'normal', 'player has no track content to load'

    def show_track_content(self):
        pass

    def hide_track_content(self):
        pass

    def show_x_content(self):
        self.mon.trace(self, '')
        # background colour
        if self.background_colour != '':
            self.canvas.config(bg=self.background_colour)
        # print 'showing background_obj: ', self.background_obj
        # reveal background image and text
        self.canvas.itemconfig(self.background_obj, state='normal')
        self.show_track_content()

        self.canvas.itemconfig(self.show_text_obj, state='normal')
        self.canvas.itemconfig(self.track_text_obj, state='normal')
        self.canvas.itemconfig(self.hint_obj, state='normal')
        # self.canvas.update_idletasks( )

        # decide whether the show background should be enabled.
        # print 'DISPLAY SHOW BG',self.track_params['display-show-background'],self.background_obj
        if self.background_obj is None and self.track_params[
                'display-show-background'] == 'yes':
            self.enable_show_background = True
        else:
            self.enable_show_background = False
        # print 'ENABLE SB',self.enable_show_background

    def hide_x_content(self):
        self.mon.trace(self, '')
        self.hide_track_content()
        self.canvas.itemconfig(self.background_obj, state='hidden')
        self.canvas.itemconfig(self.show_text_obj, state='hidden')
        self.canvas.itemconfig(self.track_text_obj, state='hidden')
        self.canvas.itemconfig(self.hint_obj, state='hidden')
        # self.canvas.update_idletasks( )

        self.canvas.delete(self.background_obj)
        self.canvas.delete(self.show_text_obj)
        self.canvas.delete(self.track_text_obj)
        self.canvas.delete(self.hint_obj)
        self.background = None
        # self.canvas.update_idletasks( )

# ****************
# utilities
# *****************

    def get_links(self):
        return self.track_params['links']

    # produce an absolute path from the relative one in track paramters
    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        elif track_file[0] == "@":
            track_file = self.pp_profile + track_file[1:]
        return track_file

    # get a text string from resources.cfg
    def resource(self, section, item):
        value = self.rr.get(section, item)
        return value  # False if not found
class MediaShow:


# *******************
# External interface
# ********************

    def __init__(self,
                            show_params,
                             root,
                            canvas,
                            showlist,
                            pp_dir,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the dictionary fo the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_params =show_params
        self.showlist=showlist
        self.root=root
        self.canvas=canvas
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()

        # Init variables
        self.player=None
        self.shower=None
        self.poll_for_interval_timer=None
        self.poll_for_continue_timer=None
        self.waiting_for_interval=False
        self.interval_timer=None
        self.duration_timer=None
        self.error=False
        
        self.interval_timer_signal=False
        self.end_trigger_signal=False
        self.end_mediashow_signal=False
        self.next_track_signal=False
        self.previous_track_signal=False
        self.play_child_signal = False
        self.req_next='nil'

        #create and instance of TimeOfDay scheduler so we can add events
        self.tod=TimeOfDay()

        self.state='closed'


    def play(self,show_id,end_callback,show_ready_callback, top=False,command='nil'):

        """ displays the mediashow
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """

        #instantiate the arguments
        self.show_id=show_id
        self.end_callback=end_callback
        self.show_ready_callback=show_ready_callback
        self.top=top
        self.command=command
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Starting show")

        # check  data files are available.
        self.media_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self,"Medialist file not found: "+ self.media_file)
            self.end('error',"Medialist file not found")


        #create a medialist for the mediashow and read it.
        self.medialist=MediaList(self.show_params['sequence'])
        if self.medialist.open_list(self.media_file,self.showlist.sissue())==False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self.end('error',"Version of medialist different to Pi Presents")

        #get controls for this show if top level
        controlsmanager=ControlsManager()
        if self.top==True:
            self.controls_list=controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list=controlsmanager.merge_show_controls(self.controls_list,self.show_params['controls'])


        #set up the time of day triggers for the show
        if self.show_params['trigger']in('time','time-quiet'):
            error_text=self.tod.add_times(self.show_params['trigger-input'],id(self),self.tod_start_callback,self.show_params['trigger'])
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)

        if self.show_params['trigger-end']=='time':
            # print self.show_params['trigger-end-time']
            error_text=self.tod.add_times(self.show_params['trigger-end-time'],id(self),self.tod_end_callback,'n/a')
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)
                
        if self.show_params['trigger-end']=='duration':
            error_text=self.calculate_duration(self.show_params['trigger-end-time'])
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)
                
        self.state='closed'
        self.egg_timer=None
        self.wait_for_trigger()


# ********************************
# Respond to external events
# ********************************

    #stop received from another concurrent show
    def managed_stop(self):
           # if next lower show is running pass down to stop the show and lower level
            if self.shower<>None:
                self.shower.managed_stop()
            else:
                #stop the show if not at top
                self.end_mediashow_signal=True
                # and if track is runing stop that first
                if self.player<>None:
                    self.player.input_pressed('stop')

    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.shower.terminate(reason)
        elif self.player<>None:
            self.player.terminate(reason)
        else:
            self.end(reason,' terminated with no shower or player to terminate')


   
   # respond to input events
    def input_pressed(self,symbol,edge,source):
        self.mon.log(self, self.show_params['show-ref']+ ' '+ str(self.show_id)+": received input: " + symbol)

        
        #  check symbol against mediashow triggers, triggers can be used at top or lower level
        # and not affected by disable-controls

        if self.state=='waiting' and self.show_params['trigger'] in ('input','input-quiet')and symbol == self.show_params['trigger-input']:
            self.start_show()
        elif self.state=='playing' and self.show_params['trigger-next']=='input' and symbol == self.show_params['next-input']:
            self.next()

       # internal functions are triggered only when disable-controls is  'no'
        if self.show_params['disable-controls']=='yes':
            return

        # if at top convert symbolic name to operation otherwise lower down we have received an operation
        # look through list of standard symbols to find match (symbolic-name, function name) operation =lookup (symbol
        if self.top==True:
            operation=self.lookup_control(symbol,self.controls_list)
        else:
            operation=symbol
            
   
        # print 'operation',operation
        self.do_operation(operation,edge,source)


    #service the standard inputs for this show
    def do_operation(self,operation,edge,source):
        if self.shower<>None:
            # if next lower show is running pass down to stop the show and lower level
            self.shower.input_pressed(operation,edge,source) 
        else:        
            # control this show and its tracks
            # print 'operation',operation
            if operation=='stop':
                if self.top == False:
                    # not at top so stop the current show 
                    self.end_mediashow_signal=True
                    # and if a track is running stop that first
                    if self.player<>None:
                        self.player.input_pressed('stop')
                else:
                    # top = True, just stop track if running
                    if self.player<>None:
                        self.player.input_pressed('stop')

            elif operation in ('up','down'):
                #if playing rather than waiting use keys for next or previous
                if operation=='up' and self.state=='playing':
                    self.previous()
                else:
                    self.next()

            elif operation=='play':
                # use 'play' to start child if state=playing or to trigger the show if waiting for trigger
                if self.state=='playing':
                    if self.show_params['has-child']=='yes':
                        self.play_child_signal=True
                        self.child_track_ref='pp-child-show'
                        # and stop the current track if its running
                        if self.player<>None:
                            self.player.input_pressed('stop')
                else:
                    if self.state=='waiting':
                        self.start_show()

            elif operation == 'pause':
                if self.player<>None:
                    self.player.input_pressed(operation)
                    
            #if the operation is omxplayer or mplayer runtime control then pass it to player if running
            elif operation[0:4]=='omx-' or operation[0:6]=='mplay-'or operation[0:5]=='uzbl-':
                if self.player<>None:
                    self.player.input_pressed(operation)



    def lookup_control(self,symbol,controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''


# ***************************
# Show sequencer
# ***************************

    def end_interval_timer(self):
        self.interval_timer_signal=True

    # callback from time of day scheduler
    def tod_start_callback(self):
         if self.state=='waiting' and self.show_params['trigger']in('time','time-quiet'):
            self.start_show()

    def tod_end_callback(self):
        if self.state=='playing' and self.show_params['trigger-end'] in ('time','duration'):
            self.end_trigger_signal=True
            if self.shower<>None:
                self.shower.input_pressed('stop')
            elif self.player<>None:
                self.player.input_pressed('stop')
                

    def stop(self,message):
        self.end_mediashow_signal=True
        if self.interval_timer<>None:
            self.canvas.after_cancel(self.interval_timer)

   
    def next(self):
        # stop track if running and set signal
        self.next_track_signal=True
        if self.shower<>None:
            self.shower.input_pressed("stop")
        else:
            if self.player<>None:
                self.player.input_pressed("stop")

    def previous(self):
        self.previous_track_signal=True
        if self.shower<>None:
            self.shower.input_pressed("stop")
        else:
            if self.player<>None:
                self.player.input_pressed("stop")
    
        
    # wait for trigger sets the state to waiting so that events can do a start show.    
    def wait_for_trigger(self):
        self.state='waiting'
        if self.show_ready_callback<>None:
            self.show_ready_callback()

        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Waiting for trigger: "+ self.show_params['trigger'])
        
        if self.show_params['trigger']=="input":
            # blank screen waiting for trigger if auto, otherwise display something
            if self.show_params['progress']=="manual":
                text= self.resource('mediashow','m01')
            else:
                text= self.resource('mediashow','m02')
            self.display_message(self.canvas,'text',text,0,self.start_show)


        elif self.show_params['trigger']=="input-quiet":
            # blank screen waiting for trigger
            text = self.resource('mediashow','m10')
            self.display_message(self.canvas,'text',text,0,self.start_show)
            pass

        elif self.show_params['trigger'] in ('time','time-quiet'):
            # show next show notice
            quiet=3
            # if next show is this one display text
            next_show=self.tod.next_event_time()
            if next_show[quiet]==False:
                if next_show[1]=='tomorrow':
                    text = self.resource('mediashow','m09')
                else:
                    text = self.resource('mediashow','m08')                     
                text=text.replace('%tt',next_show[0])
                self.display_message(self.canvas,'text',text,0,self.start_show)  
            
        elif self.show_params['trigger']=="start":
            self.start_show()
            
        else:
            self.mon.err(self,"Unknown trigger: "+ self.show_params['trigger'])
            self.end('error',"Unknown trigger type")



  
    def start_show(self):
        self.state='playing'
        self.direction='forward'
        # self.canvas.delete(ALL)
        # start interval timer
        if self.show_params['repeat']=="interval" and self.show_params['repeat-interval']<>0:
            self.interval_timer_signal=False
            self.interval_timer=self.canvas.after(int(self.show_params['repeat-interval'])*1000,self.end_interval_timer)
            
        # start duration timer
        if self.show_params['trigger-end']=='duration':
            # print 'set alarm ', self.duration
            self.duration_timer = self.canvas.after(self.duration*1000,self.tod_end_callback)
        
        # and play the first track unless commanded otherwise
        if self.command=='backward':
            self.medialist.finish()
        else:
            self.medialist.start()
        self.play_selected_track(self.medialist.selected_track())
 
 
    def what_next(self):
        self.direction='forward'

        # end of show trigger caused by tod
        if self.end_trigger_signal==True:
            self.end_trigger_signal=False
            if self.top==True:
                self.state='waiting'
                self.wait_for_trigger()
            else:
                # not at top so stop the show
                self.end('normal','sub-show end time trigger')
        
        # user wants to end, wait for any shows or tracks to have ended then end show
        # probalby will get here with end_m set when player and shower has finished
        elif self.end_mediashow_signal==True:
            if self.player==None and self.shower==None:
                self.end_mediashow_signal=False
                self.end('normal',"show ended by user")

            
        #returning from a subshow needing to move onward 
        elif self.req_next=='do-next':
            self.req_next='nil'
            self.medialist.next(self.show_params['sequence'])
            self.play_selected_track(self.medialist.selected_track())
            
        #returning from a subshow needing to move backward 
        elif self.req_next=='do-previous':
            self.req_next='nil'
            self.direction='backward'
            self.medialist.previous(self.show_params['sequence'])
            self.play_selected_track(self.medialist.selected_track())         
               
        # user wants to play child
        elif self.play_child_signal == True:
            self.play_child_signal=False
            index = self.medialist.index_of_track(self.child_track_ref)
            if index >=0:
                #don't use select the track as need to preserve mediashow sequence.
                child_track=self.medialist.track(index)
                self.display_eggtimer(self.resource('mediashow','m07'))
                self.play_selected_track(child_track)
            else:
                self.mon.err(self,"Child show not found in medialist: "+ self.show_params['pp-child-show'])
                self.end('error',"child show not found in medialist")
        
        # skip to next track on user input
        elif self.next_track_signal==True:
            self.next_track_signal=False
            if self.medialist.at_end()==True:
                if  self.show_params['sequence']=="ordered" and self.show_params['repeat']=='oneshot' and self.top==False:
                    self.end('do-next',"Return from Sub Show")
                elif  self.show_params['sequence']=="ordered" and self.show_params['repeat']=='single-run' and self.top==False:
                    self.end('do-next',"Return from Sub Show")
                else:
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())               
            else:
                self.medialist.next(self.show_params['sequence'])
                self.play_selected_track(self.medialist.selected_track())
                
        # skip to previous track on user input
        elif self.previous_track_signal==True:
            self.previous_track_signal=False
            self.direction='backward'
            if self.medialist.at_start()==True:
                if  self.show_params['sequence']=="ordered" and self.show_params['repeat']=='oneshot' and self.top==False:
                    self.end('do-previous',"Return from Sub Show")
                elif  self.show_params['sequence']=="ordered" and self.show_params['repeat']=='single-run' and self.top==False:
                    self.end('do-previous',"Return from Sub Show")
                else:
                    self.medialist.previous(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())               
            else:
                self.medialist.previous(self.show_params['sequence'])              
                self.play_selected_track(self.medialist.selected_track())
        

        # track is finished and we are on auto        
        elif self.show_params['progress']=="auto":
            
            if self.medialist.at_end()==True:

                # oneshot
                if self.show_params['repeat']=='oneshot' and self.top==False:
                    self.end('normal',"End of Oneshot in subshow")
                    
                elif self.show_params['sequence']=="ordered" and self.show_params['repeat']=='oneshot' and self.top==True:
                    self.wait_for_trigger()

                # single run
                elif self.show_params['repeat']=='single-run' and self.top==True:
                   self.end('normal',"End of Single Run")

                elif self.show_params['repeat']=='single-run' and self.top==False:
                   self.end('do-next',"End of single run - Return from Sub Show")

                # repeating and waiting to restart 
                elif self.waiting_for_interval==True:
                    if self.interval_timer_signal==True:
                        self.interval_timer_signal=False
                        self.waiting_for_interval=False
                        self.start_show()
                    else:
                        self.poll_for_interval_timer=self.canvas.after(1000,self.what_next)
 
                elif self.show_params['repeat']=='interval' and int(self.show_params['repeat-interval'])>0:
                    self.waiting_for_interval=True
                    self.poll_for_interval_timer=self.canvas.after(1000,self.what_next) 
                    
                #elif self.show_params['sequence']=="ordered" and self.show_params['repeat']=='interval' and int(self.show_params['repeat-interval'])==0:
                elif self.show_params['repeat']=='interval' and int(self.show_params['repeat-interval'])==0:
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())

                # shuffling so there is no end condition
                elif self.show_params['sequence']=="shuffle":
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())
                    
                else:
                    self.mon.err(self,"Unhandled playing event: "+self.show_params['sequence'] +' with ' + self.show_params['repeat']+" of "+ self.show_params['repeat-interval'])
                    self.end('error',"Unhandled playing event")
                    
            else:
                self.medialist.next(self.show_params['sequence'])
                self.play_selected_track(self.medialist.selected_track())
                    
        # track has finished and we are on manual progress               
        elif self.show_params['progress']=="manual":
                    self.delete_eggtimer()
                    self.canvas.delete('pp-content')
                    if self.show_params['trigger-next']=='input':
                        self.display_eggtimer(self.resource('mediashow','m03'))
                    self.poll_for_continue_timer=self.canvas.after(2000,self.what_next)
                    
        else:
            #unhandled state
            self.mon.err(self,"Unhandled playing event: ")
            self.end('error',"Unhandled playing event")           


# ***************************
# Dispatching to Players/Shows 
# ***************************
   
    def ready_callback(self):
        self.delete_eggtimer()

    def play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
        self.delete_eggtimer()
        if self.show_params['progress']=="manual":
            self.display_eggtimer(self.resource('mediashow','m04'))

        # is menu required
        if self.show_params['has-child']=="yes":
            self.enable_child=True
        else:
            self.enable_child=False

        #dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Track type is: "+ track_type)
        if track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=self.enable_child)
  
        elif track_type=="audio":
            # create a audioplayer
            track_file=self.complete_path(selected_track)
            self.player=AudioPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=self.enable_child)
 
        elif track_type=="web":
            # create a browser
            track_file=self.complete_path(selected_track)
            self.player=BrowserPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=self.enable_child)
  


 
        elif track_type=="image":
            track_file=self.complete_path(selected_track)
            # images played from menus don't have children
            self.player=ImagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                    self.showlist,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=self.enable_child)
                                    
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(text,
                                    self.showlist,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=self.enable_child
                                    )

 
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self.end('error',"Unknown show")
                
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                               self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                               self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                print "Starting MediaShow: {0}".format(selected_track['sub-show'])
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command=self.direction)

            elif selected_show['type']=="liveshow":    
                self.shower= LiveShow(selected_show,
                                                                self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="radiobuttonshow":
                self.shower= RadioButtonShow(selected_show,
                                                         self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="hyperlinkshow":
                self.shower= HyperlinkShow(selected_show,
                                                       self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                       self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')
            
            elif selected_show['type']=="menu":
                self.shower= MenuShow(selected_show,
                                                        self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                          self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')
                
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self.end('error'"Unknown show type")  
            
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self.end('error',"Unknown track type")            


    def end_player(self,reason,message):
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Returned from player with message: "+ message)
        self.player=None
        self.req_next='nil'
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            # elif>else move to what-next?
            if self.show_params['progress']=="manual":
                self.display_eggtimer(self.resource('mediashow','m05'))
                self.req_next=reason
                self.what_next()
            else:
                self.req_next=reason
                self.what_next()
                

    def end_shower(self,show_id,reason,message):
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Returned from shower with message: "+ message)
        self.shower=None
        self.req_next='nil'
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            if self.show_params['progress']=="manual":
                self.display_eggtimer(self.resource('mediashow','m06'))
                self.req_next=reason
                self.what_next() 
            else:
                self.req_next=reason
                self.what_next() 



# ***************************
# end of show 
# ***************************

    def end(self,reason,message):
        self.end_mediashow_signal=False
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Ending Mediashow")
        self.tidy_up()
        self.end_callback(self.show_id,reason,message)
        self=None
        return

    def tidy_up(self):
        #clear outstanding time of day events for this show
        # self.tod.clear_times_list(id(self))
        if self.poll_for_continue_timer<>None:
                self.canvas.after_cancel(self.poll_for_continue_timer)
                self.poll_for_continue_timer=None
        if self.poll_for_interval_timer<>None:
                self.canvas.after_cancel(self.poll_for_interval_timer)
                self.poll_for_interval_timer=None
        if self.interval_timer<>None:
            self.canvas.after_cancel(self.interval_timer)
            self.interval_timer=None
        if self.duration_timer<>None:
            self.canvas.after_cancel(self.duration_timer)
            self.duration_timer=None


# ***************************
# displaying things
# ***************************
    
    def display_eggtimer(self,text):
        self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold",
                                                tag='pp-eggtimer')
        self.canvas.update_idletasks( )


    def delete_eggtimer(self):
        self.canvas.delete('pp-eggtimer')
        self.canvas.update_idletasks( )


    # used to display internal messages in situations where a medialist entry could not be used.
    def display_message(self,canvas,source,content,duration,_display_message_callback):
            self.display_message_callback=_display_message_callback
            tp={'duration':duration,'message-colour':'white','message-font':'Helvetica 20 bold','background-colour':'',
                'message-justify':'left','background-image':'','show-control-begin':'','show-control-end':'',
                'animate-begin':'','animate-clear':'','animate-end':'','message-x':'','message-y':'',
                'display-show-background':'no','display-show-text':'no','show-text':'','track-text':'',
                'plugin':''}
            self.player=MessagePlayer(self.show_id,self.root,canvas,tp,tp,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(content,self.showlist,self.display_message_end,None,False)

    def   display_message_end(self,reason,message):
        self.player=None
        if reason in ('error','killed'):
            self.end(reason,message)
        else:
            self.display_message_callback()



# ***************************
# utilities
# ***************************

    def calculate_duration(self,line):
        fields=line.split(':')
        if len(fields)==1:
            secs=fields[0]
            minutes='0'
            hours='0'
        if len(fields)==2:
            secs=fields[1]
            minutes=fields[0]
            hours='0'
        if len(fields)==3:
            secs=fields[2]
            minutes=fields[1]
            hours=fields[0]
        self.duration=3600*long(hours)+60*long(minutes)+long(secs)
        return ''

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value
        
    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file<>'' and track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,self.show_params['show-ref']+ ' '+ str(self.show_id)+ ": Track to play is: "+ track_file)
        return track_file     
Example #37
0
class MediaShow:

    # *******************
    # External interface
    # ********************

    def __init__(self, show_params, root, canvas, showlist, pp_dir, pp_home,
                 pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the dictionary fo the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon = Monitor()
        self.mon.on()

        #instantiate arguments
        self.show_params = show_params
        self.showlist = showlist
        self.root = root
        self.canvas = canvas
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # open resources
        self.rr = ResourceReader()

        # Init variables
        self.player = None
        self.shower = None
        self.poll_for_interval_timer = None
        self.poll_for_continue_timer = None
        self.waiting_for_interval = False
        self.interval_timer = None
        self.duration_timer = None
        self.error = False

        self.interval_timer_signal = False
        self.end_trigger_signal = False
        self.end_mediashow_signal = False
        self.next_track_signal = False
        self.previous_track_signal = False
        self.play_child_signal = False
        self.req_next = 'nil'

        #create and instance of TimeOfDay scheduler so we can add events
        self.tod = TimeOfDay()

        self.state = 'closed'

    def play(self,
             show_id,
             end_callback,
             show_ready_callback,
             top=False,
             command='nil'):
        """ displays the mediashow
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """

        #instantiate the arguments
        self.show_id = show_id
        self.end_callback = end_callback
        self.show_ready_callback = show_ready_callback
        self.top = top
        self.command = command
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Starting show")

        # check  data files are available.
        self.media_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self, "Medialist file not found: " + self.media_file)
            self.end('error', "Medialist file not found")

        #create a medialist for the mediashow and read it.
        self.medialist = MediaList()
        if self.medialist.open_list(self.media_file,
                                    self.showlist.sissue()) == False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self.end('error', "Version of medialist different to Pi Presents")

        #get controls for this show if top level
        controlsmanager = ControlsManager()
        if self.top == True:
            self.controls_list = controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list = controlsmanager.merge_show_controls(
                self.controls_list, self.show_params['controls'])

        #set up the time of day triggers for the show
        if self.show_params['trigger'] in ('time', 'time-quiet'):
            error_text = self.tod.add_times(self.show_params['trigger-input'],
                                            id(self), self.tod_start_callback,
                                            self.show_params['trigger'])
            if error_text <> '':
                self.mon.err(self, error_text)
                self.end('error', error_text)

        if self.show_params['trigger-end'] == 'time':
            # print self.show_params['trigger-end-time']
            error_text = self.tod.add_times(
                self.show_params['trigger-end-time'], id(self),
                self.tod_end_callback, 'n/a')
            if error_text <> '':
                self.mon.err(self, error_text)
                self.end('error', error_text)

        if self.show_params['trigger-end'] == 'duration':
            error_text = self.calculate_duration(
                self.show_params['trigger-end-time'])
            if error_text <> '':
                self.mon.err(self, error_text)
                self.end('error', error_text)

        self.state = 'closed'
        self.egg_timer = None
        self.wait_for_trigger()

# ********************************
# Respond to external events
# ********************************

#stop received from another concurrent show

    def managed_stop(self):
        # if next lower show is running pass down to stop the show and lower level
        if self.shower <> None:
            self.shower.managed_stop()
        else:
            #stop the show if not at top
            self.end_mediashow_signal = True
            # and if track is runing stop that first
            if self.player <> None:
                self.player.input_pressed('stop')

    # kill or error
    def terminate(self, reason):
        if self.shower <> None:
            self.shower.terminate(reason)
        elif self.player <> None:
            self.player.terminate(reason)
        else:
            self.end(reason,
                     ' terminated with no shower or player to terminate')

# respond to input events

    def input_pressed(self, symbol, edge, source):
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": received input: " + symbol)

        #  check symbol against mediashow triggers, triggers can be used at top or lower level
        # and not affected by disable-controls

        if self.state == 'waiting' and self.show_params['trigger'] in (
                'input',
                'input-quiet') and symbol == self.show_params['trigger-input']:
            self.start_show()
        elif self.state == 'playing' and self.show_params[
                'trigger-next'] == 'input' and symbol == self.show_params[
                    'next-input']:
            self.next()

    # internal functions are triggered only when disable-controls is  'no'
        if self.show_params['disable-controls'] == 'yes':
            return

        # if at top convert symbolic name to operation otherwise lower down we have received an operation
        # look through list of standard symbols to find match (symbolic-name, function name) operation =lookup (symbol
        if self.top == True:
            operation = self.lookup_control(symbol, self.controls_list)
        else:
            operation = symbol

        # print 'operation',operation
        self.do_operation(operation, edge, source)

    #service the standard inputs for this show

    def do_operation(self, operation, edge, source):
        if self.shower <> None:
            # if next lower show is running pass down to stop the show and lower level
            self.shower.input_pressed(operation, edge, source)
        else:
            # control this show and its tracks
            # print 'operation',operation
            if operation == 'stop':
                if self.top == False:
                    # not at top so stop the current show
                    self.end_mediashow_signal = True
                    # and if a track is running stop that first
                    if self.player <> None:
                        self.player.input_pressed('stop')
                else:
                    # top = True, just stop track if running
                    if self.player <> None:
                        self.player.input_pressed('stop')

            elif operation in ('up', 'down'):
                #if playing rather than waiting use keys for next or previous
                if operation == 'up' and self.state == 'playing':
                    self.previous()
                else:
                    self.next()

            elif operation == 'play':
                # use 'play' to start child if state=playing or to trigger the show if waiting for trigger
                if self.state == 'playing':
                    if self.show_params['has-child'] == 'yes':
                        self.play_child_signal = True
                        self.child_track_ref = 'pp-child-show'
                        # and stop the current track if its running
                        if self.player <> None:
                            self.player.input_pressed('stop')
                else:
                    if self.state == 'waiting':
                        self.start_show()

            elif operation == 'pause':
                if self.player <> None:
                    self.player.input_pressed(operation)

            #if the operation is omxplayer or mplayer runtime control then pass it to player if running
            elif operation[0:4] == 'omx-' or operation[
                    0:6] == 'mplay-' or operation[0:5] == 'uzbl-':
                if self.player <> None:
                    self.player.input_pressed(operation)

    def lookup_control(self, symbol, controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''

# ***************************
# Show sequencer
# ***************************

    def end_interval_timer(self):
        self.interval_timer_signal = True

    # callback from time of day scheduler
    def tod_start_callback(self):
        if self.state == 'waiting' and self.show_params['trigger'] in (
                'time', 'time-quiet'):
            self.start_show()

    def tod_end_callback(self):
        if self.state == 'playing' and self.show_params['trigger-end'] in (
                'time', 'duration'):
            self.end_trigger_signal = True
            if self.shower <> None:
                self.shower.input_pressed('stop')
            elif self.player <> None:
                self.player.input_pressed('stop')

    def stop(self, message):
        self.end_mediashow_signal = True
        if self.interval_timer <> None:
            self.canvas.after_cancel(self.interval_timer)

    def next(self):
        # stop track if running and set signal
        self.next_track_signal = True
        if self.shower <> None:
            self.shower.input_pressed("stop")
        else:
            if self.player <> None:
                self.player.input_pressed("stop")

    def previous(self):
        self.previous_track_signal = True
        if self.shower <> None:
            self.shower.input_pressed("stop")
        else:
            if self.player <> None:
                self.player.input_pressed("stop")

    # wait for trigger sets the state to waiting so that events can do a start show.
    def wait_for_trigger(self):
        self.state = 'waiting'
        if self.show_ready_callback <> None:
            self.show_ready_callback()

        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Waiting for trigger: " + self.show_params['trigger'])

        if self.show_params['trigger'] == "input":
            # blank screen waiting for trigger if auto, otherwise display something
            if self.show_params['progress'] == "manual":
                text = self.resource('mediashow', 'm01')
            else:
                text = self.resource('mediashow', 'm02')
            self.display_message(self.canvas, 'text', text, 0, self.start_show)

        elif self.show_params['trigger'] == "input-quiet":
            # blank screen waiting for trigger
            text = self.resource('mediashow', 'm10')
            self.display_message(self.canvas, 'text', text, 0, self.start_show)
            pass

        elif self.show_params['trigger'] in ('time', 'time-quiet'):
            # show next show notice
            quiet = 3
            # if next show is this one display text
            next_show = self.tod.next_event_time()
            if next_show[quiet] == False:
                if next_show[1] == 'tomorrow':
                    text = self.resource('mediashow', 'm09')
                else:
                    text = self.resource('mediashow', 'm08')
                text = text.replace('%tt', next_show[0])
                self.display_message(self.canvas, 'text', text, 0,
                                     self.start_show)

        elif self.show_params['trigger'] == "start":
            self.start_show()

        else:
            self.mon.err(self,
                         "Unknown trigger: " + self.show_params['trigger'])
            self.end('error', "Unknown trigger type")

    def start_show(self):
        self.state = 'playing'
        self.direction = 'forward'
        # self.canvas.delete(ALL)
        # start interval timer
        if self.show_params['repeat'] == "interval" and self.show_params[
                'repeat-interval'] <> 0:
            self.interval_timer_signal = False
            self.interval_timer = self.canvas.after(
                int(self.show_params['repeat-interval']) * 1000,
                self.end_interval_timer)

        # start duration timer
        if self.show_params['trigger-end'] == 'duration':
            # print 'set alarm ', self.duration
            self.duration_timer = self.canvas.after(self.duration * 1000,
                                                    self.tod_end_callback)

        # and play the first track unless commanded otherwise
        if self.command == 'backward':
            self.medialist.finish()
        else:
            self.medialist.start()
        self.play_selected_track(self.medialist.selected_track())

    def what_next(self):
        self.direction = 'forward'

        # end of show trigger caused by tod
        if self.end_trigger_signal == True:
            self.end_trigger_signal = False
            if self.top == True:
                self.state = 'waiting'
                self.wait_for_trigger()
            else:
                # not at top so stop the show
                self.end('normal', 'sub-show end time trigger')

        # user wants to end, wait for any shows or tracks to have ended then end show
        # probalby will get here with end_m set when player and shower has finished
        elif self.end_mediashow_signal == True:
            if self.player == None and self.shower == None:
                self.end_mediashow_signal = False
                self.end('normal', "show ended by user")

        #returning from a subshow needing to move onward
        elif self.req_next == 'do-next':
            self.req_next = 'nil'
            self.medialist.next(self.show_params['sequence'])
            self.play_selected_track(self.medialist.selected_track())

        #returning from a subshow needing to move backward
        elif self.req_next == 'do-previous':
            self.req_next = 'nil'
            self.direction = 'backward'
            self.medialist.previous(self.show_params['sequence'])
            self.play_selected_track(self.medialist.selected_track())

        # user wants to play child
        elif self.play_child_signal == True:
            self.play_child_signal = False
            index = self.medialist.index_of_track(self.child_track_ref)
            if index >= 0:
                #don't use select the track as need to preserve mediashow sequence.
                child_track = self.medialist.track(index)
                self.display_eggtimer(self.resource('mediashow', 'm07'))
                self.play_selected_track(child_track)
            else:
                self.mon.err(
                    self, "Child show not found in medialist: " +
                    self.show_params['pp-child-show'])
                self.end('error', "child show not found in medialist")

        # skip to next track on user input
        elif self.next_track_signal == True:
            self.next_track_signal = False
            if self.medialist.at_end() == True:
                if self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'oneshot' and self.top == False:
                    self.end('do-next', "Return from Sub Show")
                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'single-run' and self.top == False:
                    self.end('do-next', "Return from Sub Show")
                else:
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())
            else:
                self.medialist.next(self.show_params['sequence'])
                self.play_selected_track(self.medialist.selected_track())

        # skip to previous track on user input
        elif self.previous_track_signal == True:
            self.previous_track_signal = False
            self.direction = 'backward'
            if self.medialist.at_start() == True:
                if self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'oneshot' and self.top == False:
                    self.end('do-previous', "Return from Sub Show")
                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'single-run' and self.top == False:
                    self.end('do-previous', "Return from Sub Show")
                else:
                    self.medialist.previous(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())
            else:
                self.medialist.previous(self.show_params['sequence'])
                self.play_selected_track(self.medialist.selected_track())

        # track is finished and we are on auto
        elif self.show_params['progress'] == "auto":

            if self.medialist.at_end() == True:

                # oneshot
                if self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'oneshot' and self.top == False:
                    self.end('normal', "End of Oneshot in subshow")

                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'oneshot' and self.top == True:
                    self.wait_for_trigger()

                # single run
                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'single-run' and self.top == True:
                    self.end('normal', "End of Single Run")

                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'single-run' and self.top == False:
                    self.end('do-next',
                             "End of single run - Return from Sub Show")

                # repeating and waiting to restart
                elif self.waiting_for_interval == True:
                    if self.interval_timer_signal == True:
                        self.interval_timer_signal = False
                        self.waiting_for_interval = False
                        self.start_show()
                    else:
                        self.poll_for_interval_timer = self.canvas.after(
                            1000, self.what_next)

                elif self.show_params[
                        'sequence'] == "ordered" and self.show_params[
                            'repeat'] == 'interval' and int(
                                self.show_params['repeat-interval']) > 0:
                    self.waiting_for_interval = True
                    self.poll_for_interval_timer = self.canvas.after(
                        1000, self.what_next)

                #elif self.show_params['sequence']=="ordered" and self.show_params['repeat']=='interval' and int(self.show_params['repeat-interval'])==0:
                elif self.show_params['repeat'] == 'interval' and int(
                        self.show_params['repeat-interval']) == 0:
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())

                # shuffling so there is no end condition
                elif self.show_params['sequence'] == "shuffle":
                    self.medialist.next(self.show_params['sequence'])
                    self.play_selected_track(self.medialist.selected_track())

                else:
                    self.mon.err(
                        self, "Unhandled playing event: " +
                        self.show_params['sequence'] + ' with ' +
                        self.show_params['repeat'] + " of " +
                        self.show_params['repeat-interval'])
                    self.end('error', "Unhandled playing event")

            else:
                self.medialist.next(self.show_params['sequence'])
                self.play_selected_track(self.medialist.selected_track())

        # track has finished and we are on manual progress
        elif self.show_params['progress'] == "manual":
            self.delete_eggtimer()
            self.canvas.delete('pp-content')
            if self.show_params['trigger-next'] == 'input':
                self.display_eggtimer(self.resource('mediashow', 'm03'))
            self.poll_for_continue_timer = self.canvas.after(
                2000, self.what_next)

        else:
            #unhandled state
            self.mon.err(self, "Unhandled playing event: ")
            self.end('error', "Unhandled playing event")

# ***************************
# Dispatching to Players/Shows
# ***************************

    def ready_callback(self):
        self.delete_eggtimer()

    def play_selected_track(self, selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
        self.delete_eggtimer()
        if self.show_params['progress'] == "manual":
            self.display_eggtimer(self.resource('mediashow', 'm04'))

        # is menu required
        if self.show_params['has-child'] == "yes":
            self.enable_child = True
        else:
            self.enable_child = False

        #dispatch track by type
        self.player = None
        self.shower = None
        track_type = selected_track['type']
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Track type is: " + track_type)
        if track_type == "video":
            # create a videoplayer
            track_file = self.complete_path(selected_track)
            self.player = VideoPlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=self.enable_child)

        elif track_type == "audio":
            # create a audioplayer
            track_file = self.complete_path(selected_track)
            self.player = AudioPlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=self.enable_child)

        elif track_type == "web":
            # create a browser
            track_file = self.complete_path(selected_track)
            self.player = BrowserPlayer(self.show_id, self.root, self.canvas,
                                        self.show_params, selected_track,
                                        self.pp_dir, self.pp_home,
                                        self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=self.enable_child)

        elif track_type == "image":
            track_file = self.complete_path(selected_track)
            # images played from menus don't have children
            self.player = ImagePlayer(self.show_id, self.root, self.canvas,
                                      self.show_params, selected_track,
                                      self.pp_dir, self.pp_home,
                                      self.pp_profile)
            self.player.play(track_file,
                             self.showlist,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=self.enable_child)

        elif track_type == "message":
            # bit odd because MessagePlayer is used internally to display text.
            text = selected_track['text']
            self.player = MessagePlayer(self.show_id, self.root, self.canvas,
                                        self.show_params, selected_track,
                                        self.pp_dir, self.pp_home,
                                        self.pp_profile)
            self.player.play(text,
                             self.showlist,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=self.enable_child)

        elif track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >= 0:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
            else:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self.end('error', "Unknown show")

            if selected_show['type'] == "mediashow":
                self.shower = MediaShow(selected_show, self.root, self.canvas,
                                        self.showlist, self.pp_dir,
                                        self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command=self.direction)

            elif selected_show['type'] == "liveshow":
                self.shower = LiveShow(selected_show, self.root, self.canvas,
                                       self.showlist, self.pp_dir,
                                       self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "radiobuttonshow":
                self.shower = RadioButtonShow(selected_show, self.root,
                                              self.canvas, self.showlist,
                                              self.pp_dir, self.pp_home,
                                              self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "hyperlinkshow":
                self.shower = HyperlinkShow(selected_show, self.root,
                                            self.canvas, self.showlist,
                                            self.pp_dir, self.pp_home,
                                            self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            elif selected_show['type'] == "menu":
                self.shower = MenuShow(selected_show, self.root, self.canvas,
                                       self.showlist, self.pp_dir,
                                       self.pp_home, self.pp_profile)
                self.shower.play(self.show_id,
                                 self.end_shower,
                                 self.ready_callback,
                                 top=False,
                                 command='nil')

            else:
                self.mon.err(self,
                             "Unknown Show Type: " + selected_show['type'])
                self.end('error' "Unknown show type")

        else:
            self.mon.err(self, "Unknown Track Type: " + track_type)
            self.end('error', "Unknown track type")

    def end_player(self, reason, message):
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Returned from player with message: " + message)
        self.player = None
        self.req_next = 'nil'
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            # elif>else move to what-next?
            if self.show_params['progress'] == "manual":
                self.display_eggtimer(self.resource('mediashow', 'm05'))
                self.req_next = reason
                self.what_next()
            else:
                self.req_next = reason
                self.what_next()

    def end_shower(self, show_id, reason, message):
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Returned from shower with message: " + message)
        self.shower = None
        self.req_next = 'nil'
        if reason in ("killed", "error"):
            self.end(reason, message)
        else:
            if self.show_params['progress'] == "manual":
                self.display_eggtimer(self.resource('mediashow', 'm06'))
                self.req_next = reason
                self.what_next()
            else:
                self.req_next = reason
                self.what_next()

# ***************************
# end of show
# ***************************

    def end(self, reason, message):
        self.end_mediashow_signal = False
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Ending Mediashow")
        self.tidy_up()
        self.end_callback(self.show_id, reason, message)
        self = None
        return

    def tidy_up(self):
        #clear outstanding time of day events for this show
        # self.tod.clear_times_list(id(self))
        if self.poll_for_continue_timer <> None:
            self.canvas.after_cancel(self.poll_for_continue_timer)
            self.poll_for_continue_timer = None
        if self.poll_for_interval_timer <> None:
            self.canvas.after_cancel(self.poll_for_interval_timer)
            self.poll_for_interval_timer = None
        if self.interval_timer <> None:
            self.canvas.after_cancel(self.interval_timer)
            self.interval_timer = None
        if self.duration_timer <> None:
            self.canvas.after_cancel(self.duration_timer)
            self.duration_timer = None

# ***************************
# displaying things
# ***************************

    def display_eggtimer(self, text):
        self.canvas.create_text(int(self.canvas['width']) / 2,
                                int(self.canvas['height']) / 2,
                                text=text,
                                fill='white',
                                font="Helvetica 20 bold",
                                tag='pp-eggtimer')
        self.canvas.update_idletasks()

    def delete_eggtimer(self):
        self.canvas.delete('pp-eggtimer')
        self.canvas.update_idletasks()

    # used to display internal messages in situations where a medialist entry could not be used.
    def display_message(self, canvas, source, content, duration,
                        _display_message_callback):
        self.display_message_callback = _display_message_callback
        tp = {
            'duration': duration,
            'message-colour': 'white',
            'message-font': 'Helvetica 20 bold',
            'background-colour': '',
            'message-justify': 'left',
            'background-image': '',
            'show-control-begin': '',
            'show-control-end': '',
            'animate-begin': '',
            'animate-clear': '',
            'animate-end': '',
            'message-x': '',
            'message-y': '',
            'display-show-background': 'no',
            'display-show-text': 'no',
            'show-text': '',
            'track-text': '',
            'plugin': ''
        }
        self.player = MessagePlayer(self.show_id, self.root, canvas, tp, tp,
                                    self.pp_dir, self.pp_home, self.pp_profile)
        self.player.play(content, self.showlist, self.display_message_end,
                         None, False)

    def display_message_end(self, reason, message):
        self.player = None
        if reason in ('error', 'killed'):
            self.end(reason, message)
        else:
            self.display_message_callback()


# ***************************
# utilities
# ***************************

    def calculate_duration(self, line):
        fields = line.split(':')
        if len(fields) == 1:
            secs = fields[0]
            minutes = '0'
            hours = '0'
        if len(fields) == 2:
            secs = fields[1]
            minutes = fields[0]
            hours = '0'
        if len(fields) == 3:
            secs = fields[2]
            minutes = fields[1]
            hours = fields[0]
        self.duration = 3600 * long(hours) + 60 * long(minutes) + long(secs)
        return ''

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self.terminate("error")
        else:
            return value

    def complete_path(self, selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file <> '' and track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Track to play is: " + track_file)
        return track_file
Example #38
0
class AudioPlayer:
    """       
            plays an audio track using mplayer against a coloured backgroud and image
            track can be paused and interrupted
            See pp_imageplayer for common software design description
    """

    #state constants
    _CLOSED = "mplayer_closed"  #probably will not exist
    _STARTING = "mplayer_starting"  #track is being prepared
    _PLAYING = "mplayer_playing"  #track is playing to the screen, may be paused
    _ENDING = "mplayer_ending"  #track is in the process of ending due to quit or end of track
    _WAITING = "wait for timeout"  # track has finished but timeout still running

    # audio mixer matrix settings
    _LEFT = "channels=2:1:0:0:1:1"
    _RIGHT = "channels=2:1:0:1:1:0"
    _STEREO = "channels=2"

    # ***************************************
    # EXTERNAL COMMANDS
    # ***************************************

    def __init__(self, show_id, root, canvas, show_params, track_params,
                 pp_dir, pp_home, pp_profile):

        self.mon = Monitor()
        self.mon.off()

        #instantiate arguments
        self.show_id = show_id
        self.root = root
        self.canvas = canvas
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # get duration limit (secs ) from profile
        if self.track_params['duration'] <> '':
            self.duration = int(self.track_params['duration'])
            self.duration_limit = 20 * self.duration
        else:
            self.duration_limit = -1

        # get background image from profile.
        self.background_file = ''
        if self.track_params['background-image'] <> "":
            self.background_file = self.track_params['background-image']
        else:
            if self.track_params['display-show-background'] == 'yes':
                self.background_file = self.show_params['background-image']

        # get background colour from profile.
        if self.track_params['background-colour'] <> "":
            self.background_colour = self.track_params['background-colour']
        else:
            self.background_colour = self.show_params['background-colour']

        # get audio device from profile.
        if self.track_params['mplayer-audio'] <> "":
            self.mplayer_audio = self.track_params['mplayer-audio']
        else:
            self.mplayer_audio = self.show_params['mplayer-audio']

        # get audio volume from profile.
        if self.track_params['mplayer-volume'] <> "":
            self.mplayer_volume = self.track_params['mplayer-volume'].strip()
        else:
            self.mplayer_volume = self.show_params['mplayer-volume'].strip()
        self.volume_option = 'volume=' + self.mplayer_volume

        #get speaker from profile
        if self.track_params['audio-speaker'] <> "":
            self.audio_speaker = self.track_params['audio-speaker']
        else:
            self.audio_speaker = self.show_params['audio-speaker']

        if self.audio_speaker == 'left':
            self.speaker_option = AudioPlayer._LEFT
        elif self.audio_speaker == 'right':
            self.speaker_option = AudioPlayer._RIGHT
        else:
            self.speaker_option = AudioPlayer._STEREO

        #get animation instructions from profile
        self.animate_begin_text = self.track_params['animate-begin']
        self.animate_end_text = self.track_params['animate-end']

        # open the plugin Manager
        self.pim = PluginManager(self.show_id, self.root, self.canvas,
                                 self.show_params, self.track_params,
                                 self.pp_dir, self.pp_home, self.pp_profile)

        #create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()

        # could put instance generation in play, not sure which is better.
        self.mplayer = mplayerDriver(self.canvas)
        self.tick_timer = None
        self.init_play_state_machine()

    def play(self,
             track,
             showlist,
             end_callback,
             ready_callback,
             enable_menu=False):

        #instantiate arguments
        self.track = track
        self.showlist = showlist
        self.end_callback = end_callback  # callback when finished
        self.ready_callback = ready_callback  #callback when ready to play
        self.enable_menu = enable_menu

        # select the sound device
        if self.mplayer_audio <> "":
            if self.mplayer_audio == 'hdmi':
                os.system("amixer -q -c 0 cset numid=3 2")
            else:
                os.system("amixer -q -c 0 cset numid=3 1")

        # callback to the calling object to e.g remove egg timer.
        if self.ready_callback <> None:
            self.ready_callback()

        # create an  instance of showmanager so we can control concurrent shows
        self.show_manager = ShowManager(self.show_id, self.showlist,
                                        self.show_params, self.root,
                                        self.canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)

        # Control other shows at beginning
        reason, message = self.show_manager.show_control(
            self.track_params['show-control-begin'])
        if reason == 'error':
            self.mon.err(self, message)
            self.end_callback(reason, message)
            self = None
        else:
            # display image and text
            reason, message = self.display_content()
            if reason == 'error':
                self.mon.err(self, message)
                self.end_callback(reason, message)
                self = None
            else:
                # create animation events
                reason, message = self.ppio.animate(self.animate_begin_text,
                                                    id(self))
                if reason == 'error':
                    self.mon.err(self, message)
                    self.end_callback(reason, message)
                    self = None
                else:
                    # start playing the track.
                    if self.duration_limit <> 0:
                        self.start_play_state_machine()
                    else:
                        self.tick_timer = self.canvas.after(10, self.end_zero)

    def end_zero(self):
        self.end('normal', 'zero duration')

    def terminate(self, reason):
        """
        terminate the  player in special circumstances
        normal user termination if by key_pressed 'stop'
        reason will be killed or error
        """
        # circumvents state machine to terminate lower level and then itself.
        if self.mplayer <> None:
            self.mon.log(self, "sent terminate to mplayerdriver")
            self.mplayer.terminate(reason)
            self.end('killed', ' end without waiting for mplayer to finish'
                     )  # end without waiting
        else:
            self.mon.log(self, "terminate, mplayerdriver not running")
            self.end('killed', 'terminate, mplayerdriver not running')

    def get_links(self):
        return self.track_params['links']

    def input_pressed(self, symbol):
        if symbol[0:6] == 'mplay-':
            self.control(symbol[6])

        elif symbol == 'pause':
            self.pause()

        elif symbol == 'stop':
            self.stop()

# ***************************************
# INTERNAL FUNCTIONS
# ***************************************

#toggle pause

    def pause(self):
        if self.play_state in (AudioPlayer._PLAYING,
                               AudioPlayer._ENDING) and self.track <> '':
            self.mplayer.pause()
            return True
        else:
            self.mon.log(self, "!<pause rejected")
            return False

    # other control when playing, not currently used
    def control(self, char):
        if self.play_state == AudioPlayer._PLAYING and self.track <> '' and char not in (
                'q'):
            self.mon.log(self, "> send control to mplayer: " + char)
            self.mplayer.control(char)
            return True
        else:
            self.mon.log(self, "!<control rejected")
            return False

    # respond to normal stop
    def stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self, ">stop received")
        self.quit_signal = True

# ***************************************
#  sequencing
# ***************************************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the mplayer process is not running, mplayer process can be initiated
         - _starting - mplayer process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - mplayer is doing its termination, controls cannot be sent
    """
    def init_play_state_machine(self):
        self.quit_signal = False
        self.play_state = AudioPlayer._CLOSED

    def start_play_state_machine(self):
        #initialise all the state machine variables
        self.duration_count = 0
        self.quit_signal = False  # signal that user has pressed stop

        #play the track
        options = self.show_params[
            'mplayer-other-options'] + '-af ' + self.speaker_option + ',' + self.volume_option + ' '
        if self.track <> '':
            self.mplayer.play(self.track, options)
            self.mon.log(self,
                         'Playing track from show Id: ' + str(self.show_id))
            self.play_state = AudioPlayer._STARTING
        else:
            self.play_state = AudioPlayer._PLAYING
        # and start polling for state changes and count duration
        self.tick_timer = self.canvas.after(50, self.play_state_machine)

    def play_state_machine(self):
        self.duration_count += 1

        if self.play_state == AudioPlayer._CLOSED:
            self.mon.log(self, "      State machine: " + self.play_state)
            return

        elif self.play_state == AudioPlayer._STARTING:
            self.mon.log(self, "      State machine: " + self.play_state)

            # if mplayer is playing the track change to play state
            if self.mplayer.start_play_signal == True:
                self.mon.log(
                    self,
                    "            <start play signal received from mplayer")
                self.mplayer.start_play_signal = False
                self.play_state = AudioPlayer._PLAYING
                self.mon.log(self,
                             "      State machine: mplayer_playing started")
            self.tick_timer = self.canvas.after(50, self.play_state_machine)

        elif self.play_state == AudioPlayer._PLAYING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # service any queued stop signals
            if self.quit_signal == True or (
                    self.duration_limit > 0
                    and self.duration_count > self.duration_limit):
                self.mon.log(self,
                             "      Service stop required signal or timeout")
                # self.quit_signal=False
                if self.track <> '':
                    self.stop_mplayer()
                    self.play_state = AudioPlayer._ENDING
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self.end('normal', 'stop required signal or timeout')

            # mplayer reports it is terminating so change to ending state
            if self.track <> '' and self.mplayer.end_play_signal:
                self.mon.log(self, "            <end play signal received")
                self.mon.log(
                    self, "            <end detected at: " +
                    str(self.mplayer.audio_position))
                self.play_state = AudioPlayer._ENDING
            self.tick_timer = self.canvas.after(50, self.play_state_machine)

        elif self.play_state == AudioPlayer._ENDING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # if spawned process has closed can change to closed state
            # self.mon.log (self,"      State machine : is mplayer process running? -  "  + str(self.mplayer.is_running()))
            if self.mplayer.is_running() == False:
                self.mon.log(self, "            <mplayer process is dead")
                if self.quit_signal == True:
                    self.quit_signal = False
                    self.play_state = AudioPlayer._CLOSED
                    self.end('normal', 'quit required or timeout')
                elif self.duration_limit > 0 and self.duration_count < self.duration_limit:
                    self.play_state = AudioPlayer._WAITING
                    self.tick_timer = self.canvas.after(
                        50, self.play_state_machine)
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self.end('normal', 'mplayer dead')
            else:
                self.tick_timer = self.canvas.after(50,
                                                    self.play_state_machine)

        elif self.play_state == AudioPlayer._WAITING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            if self.quit_signal == True or (
                    self.duration_limit > 0
                    and self.duration_count > self.duration_limit):
                self.mon.log(
                    self,
                    "      Service stop required signal or timeout from wait")
                self.quit_signal = False
                self.play_state = AudioPlayer._CLOSED
                self.end('normal', 'mplayer dead')
            else:
                self.tick_timer = self.canvas.after(50,
                                                    self.play_state_machine)

    def stop_mplayer(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,
                     "         >stop mplayer received from state machine")
        if self.play_state == AudioPlayer._PLAYING:
            self.mplayer.stop()
            return True
        else:
            self.mon.log(self, "!<stop rejected")
            return False

# *****************
# ending the player
# *****************

    def end(self, reason, message):
        # stop the plugin
        if self.track_params['plugin'] <> '':
            self.pim.stop_plugin()

        # abort the timer
        if self.tick_timer <> None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        if reason in ('error', 'killed'):
            self.end_callback(reason, message)
            self = None

        else:
            # normal end so do show control and animation

            # Control concurrent shows at end
            reason, message = self.show_manager.show_control(
                self.track_params['show-control-end'])
            if reason == 'error':
                self.mon.err(self, message)
                self.end_callback(reason, message)
                self = None
            else:
                # clear events list for this track
                if self.track_params['animate-clear'] == 'yes':
                    self.ppio.clear_events_list(id(self))

                # create animation events for ending
                reason, message = self.ppio.animate(self.animate_end_text,
                                                    id(self))
                if reason == 'error':
                    self.mon.err(self, message)
                    self.end_callback(reason, message)
                    self = None
                else:
                    self.end_callback('normal', "track has terminated or quit")
                    self = None

# *****************
# displaying things
# *****************

    def display_content(self):

        #if self.background_file<>'' or self.show_params['show-text']<> '' or #self.track_params['track-text']<> '' or self.enable_menu== True or #self.track_params['clear-screen']=='yes':

        if self.track_params['clear-screen'] == 'yes':
            self.canvas.delete('pp-content')
            # self.canvas.update()

        #background colour
        if self.background_colour <> '':
            self.canvas.config(bg=self.background_colour)

        if self.background_file <> '':
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(
                    self, "Audio background file not found: " +
                    self.background_img_file)
                self.end('error', "Audio background file not found")
            else:
                pil_background_img = PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(
                    int(self.canvas['width']) / 2,
                    int(self.canvas['height']) / 2,
                    image=self.background,
                    anchor=CENTER,
                    tag='pp-content')

        # execute the plugin if required
        if self.track_params['plugin'] <> '':

            reason, message, self.track = self.pim.do_plugin(
                self.track,
                self.track_params['plugin'],
            )
            if reason <> 'normal':
                return reason, message

        # display hint text if enabled

        if self.enable_menu == True:
            self.canvas.create_text(int(self.show_params['hint-x']),
                                    int(self.show_params['hint-y']),
                                    text=self.show_params['hint-text'],
                                    fill=self.show_params['hint-colour'],
                                    font=self.show_params['hint-font'],
                                    anchor=NW,
                                    tag='pp-content')

        # display show text if enabled
        if self.show_params['show-text'] <> '' and self.track_params[
                'display-show-text'] == 'yes':
            self.canvas.create_text(int(self.show_params['show-text-x']),
                                    int(self.show_params['show-text-y']),
                                    anchor=NW,
                                    text=self.show_params['show-text'],
                                    fill=self.show_params['show-text-colour'],
                                    font=self.show_params['show-text-font'],
                                    tag='pp-content')

        # display track text if enabled
        if self.track_params['track-text'] <> '':
            self.canvas.create_text(
                int(self.track_params['track-text-x']),
                int(self.track_params['track-text-y']),
                anchor=NW,
                text=self.track_params['track-text'],
                fill=self.track_params['track-text-colour'],
                font=self.track_params['track-text-font'],
                tag='pp-content')

        self.mon.log(self, "Displayed background and text ")

        self.canvas.tag_raise('pp-click-area')

        self.canvas.update_idletasks()

        return 'normal', ''

# *****************
# utilities
# *****************

    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Background image is " + track_file)
        return track_file
class KbdDriver(object):

    config=None

    def __init__(self):
        self.mon=Monitor()

    # sets up tkinter keyboard events such that any key press
    # does a callback to 'callback' with the event object and a symbolic name.
    def bind_keys(self,widget,callback):
        for option in KbdDriver.config.items('keys'):
            condition=option[0]
            symbolic_name=option[1]
            # print condition,symbolic_name
            widget.bind(condition, lambda event, name=symbolic_name: self.specific_key(callback,name))

        # bind all the normal keys that return a printing character such that x produces pp-key-x
        widget.bind("<Key>", lambda event : self.normal_key(callback,event))


    def specific_key(self,callback,name):
        callback(name,'KBD')

    # alphanumeric keys- convert to symbolic by adding pp-key-
    def normal_key(self,callback,event):
        key=event.char
        if key != '':
            callback('pp-key-'+key,'KBD')



     # read the key bindings from keys.cfg
    def read(self,pp_dir,pp_home,pp_profile):
        if KbdDriver.config is None:
            # try inside profile
            tryfile=pp_profile+os.sep+'pp_io_config'+os.sep+'keys.cfg'
            if os.path.exists(tryfile):
                self.mon.log(self,"Found keys.cfg in profile at: "+ tryfile)
                filename=tryfile
            else:
                # try inside pipresents
                tryfile=pp_dir+os.sep+'pp_config'+os.sep+"keys.cfg"
                if os.path.exists(tryfile):
                    filename=tryfile
                    self.mon.log(self,"Fallback keys.cfg found at "+ tryfile)
                else:
                    self.mon.log(self,"keys.cfg not found at "+ tryfile)
                    self.mon.err(self,"keys.cfg not found in profile or fallback in Pi Presents")
                    return False   
            KbdDriver.config = ConfigParser.ConfigParser()
            KbdDriver.config.optionxform=str
            KbdDriver.config.read(filename)
            self.mon.log(self,"keys.cfg read from "+ filename)
            if KbdDriver.config.has_section('keys') is False:
                self.mon.err(self,"no [keys] section in keys.cfg")
                return False
            return True

    def has_section(self,section):
        if KbdDriver.config.has_section(section) is False:
            return False
class IOPluginManager(object):

    plugins=[]

    def __init__(self):
        self.mon=Monitor()



    def init(self,pp_dir,pp_profile,widget,callback):
        self.pp_dir=pp_dir
        self.pp_profile=pp_profile
        IOPluginManager.plugins=[]

        if os.path.exists(self.pp_profile+os.sep+'pp_io_config'):
            # read the .cfg files in /pp_io_config in profile registring the I/O plugin
            for cfgfile in os.listdir(self.pp_profile+os.sep+'pp_io_config'):
                if cfgfile in ('screen.cfg','osc.cfg'):
                    continue
                cfgfilepath = self.pp_profile+os.sep+'pp_io_config'+os.sep+cfgfile
                status,message=self.init_config(cfgfile,cfgfilepath,widget,callback)
                if status == 'error':
                    return status,message

        #read .cfg file in /pipresents/pp_io_config if file not present in profile then use this one
        for cfgfile in os.listdir(self.pp_dir+os.sep+'pp_io_config'):
            if cfgfile in ('screen.cfg','osc.cfg'):
                continue
            if not os.path.exists(self.pp_profile+os.sep+'pp_io_config'+os.sep+cfgfile):
                cfgfilepath=self.pp_dir+os.sep+'pp_io_config'+os.sep+cfgfile
                status,message=self.init_config(cfgfile,cfgfilepath,widget,callback)
                if status == 'error':
                    return status,message
                 
        # print IOPluginManager.plugins
        return 'normal','I/O Plugins registered'

    def init_config(self,cfgfile,cfgfilepath,widget,callback):
        # print cfgfile,cfgfilepath
        reason,message,config=self._read(cfgfile,cfgfilepath)
        if reason =='error':
            self.mon.err(self,'Failed to read '+cfgfile + ' ' + message)
            return 'error','Failed to read '+cfgfile + ' ' + message                
        if config.has_section('DRIVER') is False:
            self.mon.err(self,'No DRIVER section in '+cfgfilepath)
            return 'error','No DRIVER section in '+cfgfilepath
        entry = dict()
        #read information from DRIVER section
        entry['title']=config.get('DRIVER','title')
        if config.get('DRIVER','enabled')=='yes':
            driver_name=config.get('DRIVER','module')
            driver_path=self.pp_dir+os.sep+'pp_io_plugins'+os.sep+driver_name+'.py'
            if not os.path.exists(driver_path):
                self.mon.err(self,driver_name + ' Driver not found in ' + driver_path)
                return 'error',driver_name + ' Driver not found in ' + driver_path
            
            instance = self._load_plugin_file(driver_name,self.pp_dir+os.sep+'pp_io_plugins')
            reason,message=instance.init(cfgfile,cfgfilepath,widget,callback)
            if reason=='warn':
                self.mon.warn(self,message)
                return 'error',message
            if reason=='error':
                self.mon.warn(self,message)
                return 'error',message
            entry['instance']=instance
            self.mon.log(self,message)
            IOPluginManager.plugins.append(entry)
        return 'normal','I/O Plugins registered'
    

    def start(self):
        for entry in IOPluginManager.plugins:
            plugin=entry['instance']
            if plugin.is_active() is True:
                plugin.start()
                
        
    def terminate(self):
        for entry in IOPluginManager.plugins:
            plugin=entry['instance']
            if plugin.is_active() is True:
                plugin.terminate()
                self.mon.log(self,'I/O plugin '+entry['title']+ ' terminated')

    def get_input(self,key):
        for entry in IOPluginManager.plugins:
            plugin=entry['instance']
            # print 'trying ',entry['title'],plugin.is_active()
            if plugin.is_active() is True:
                found,value = plugin.get_input(key)
                if found is True:
                    return found,value
        # key not found in any plugin
        return False,None

    def handle_output_event(self,name,param_type,param_values,req_time):
        for entry in IOPluginManager.plugins:
            plugin=entry['instance']
            # print 'trying ',entry['title'],name,param_type,plugin.is_active()
            if plugin.is_active() is True:
                reason,message= plugin.handle_output_event(name,param_type,param_values,req_time)
                if reason == 'error':
                    # self.mon.err(self,message)
                    return 'error',message
                else:
                    self.mon.log(self,message)
        return 'normal','output scan complete'

    def _load_plugin_file(self, name, driver_dir):
        fp, pathname,description = imp.find_module(name,[driver_dir])
        module_id =  imp.load_module(name,fp,pathname,description)
        plugin_class = getattr(module_id,name)
        return plugin_class()

        

    def _read(self,filename,filepath):
        if os.path.exists(filepath):
            config = ConfigParser.ConfigParser()
            config.read(filepath)
            self.mon.log(self,filename+" read from "+ filepath)
            return 'normal',filename+' read',config
        else:
            return 'error',filename+' not found at: '+filepath,None
Example #41
0
class VideoPlayer:
    """ plays a track using omxplayer
        See pp_imageplayer for common software design description
    """

    _CLOSED = "omx_closed"    #probably will not exist
    _STARTING = "omx_starting"  #track is being prepared
    _PLAYING = "omx_playing"  #track is playing to the screen, may be paused
    _ENDING = "omx_ending"  #track is in the process of ending due to quit or end of track


# ***************************************
# EXTERNAL COMMANDS
# ***************************************

    def __init__(self,
                         show_id,
                         root,
                        canvas,
                        show_params,
                        track_params ,
                         pp_dir,
                        pp_home,
                        pp_profile):

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_id=show_id
        self.root=root
        self.canvas = canvas
        self.show_params=show_params
        self.track_params=track_params
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile


        # get config from medialist if there.
        if self.track_params['omx-audio']<>"":
            self.omx_audio= self.track_params['omx-audio']
        else:
            self.omx_audio= self.show_params['omx-audio']
        if self.omx_audio<>"": self.omx_audio= "-o "+ self.omx_audio
        
        if self.track_params['omx-volume']<>"":
            self.omx_volume= self.track_params['omx-volume']
        else:
            self.omx_volume= self.show_params['omx-volume']
        if self.omx_volume<>"":
            self.omx_volume= "--vol "+ str(int(self.omx_volume)*100) + ' '

        if self.track_params['omx-window']<>'':
            self.omx_window= self.track_params['omx-window']
        else:
            self.omx_window= self.show_params['omx-window']


        # get background image from profile.
        self.background_file=''
        if self.track_params['background-image']<>"":
            self.background_file= self.track_params['background-image']
        else:
            if self.track_params['display-show-background']=='yes':
                self.background_file= self.show_params['background-image']
            
        # get background colour from profile.
        if self.track_params['background-colour']<>"":
            self.background_colour= self.track_params['background-colour']
        else:
            self.background_colour= self.show_params['background-colour']
        
        self.centre_x = int(self.canvas['width'])/2
        self.centre_y = int(self.canvas['height'])/2
        
        #get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']

        # open the plugin Manager
        self.pim=PluginManager(self.show_id,self.root,self.canvas,self.show_params,self.track_params,self.pp_dir,self.pp_home,self.pp_profile) 

        #create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()        
        
        # could put instance generation in play, not sure which is better.
        self.omx=OMXDriver(self.canvas)
        self.tick_timer=None
        self.init_play_state_machine()



    def play(self, track,
                     showlist,
                     end_callback,
                     ready_callback,
                     enable_menu=False):
                         
        #instantiate arguments
        self.track=track
        self.showlist=showlist
        self.ready_callback=ready_callback   #callback when ready to play
        self.end_callback=end_callback         # callback when finished
        self.enable_menu = enable_menu
 
        # callback to the calling object to e.g remove egg timer and enable click areas.
        if self.ready_callback<>None:
            self.ready_callback()

        # create an  instance of showmanager so we can control concurrent shows
        self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        #set up video window
        reason,message,comand,has_window,x1,y1,x2,y2= self.parse_window(self.omx_window)
        if reason =='error':
            self.mon.err(self,'omx window error: ' + message + ' in ' + self.omx_window)
            self.end_callback(reason,message)
        else:
            if has_window==True:
                self.omx_window= '--win " '+ str(x1) +  ' ' + str(y1) + ' ' + str(x2) + ' ' + str(y2) + ' " '
            else:
                self.omx_window=''

             # Control other shows at beginning
            reason,message=self.show_manager.show_control(self.track_params['show-control-begin'])
            if reason in ('error','killed'):
                self.end_callback(reason,message)
                self=None
            else:      
                #display content
                reason,message=self.display_content()
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                    # create animation events
                    reason,message=self.ppio.animate(self.animate_begin_text,id(self))
                    if reason=='error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        # start playing the video.
                        if self.play_state == VideoPlayer._CLOSED:
                            self.mon.log(self,">play track received")
                            self.start_play_state_machine(self.track)
                        else:
                            self.mon.err(self,'play track rejected')
                            self.end_callback('error','play track rejected')
                            self=None

    def terminate(self,reason):
        # circumvents state machine and does not wait for omxplayer to close
        if self.omx<>None:
            self.mon.log(self,"sent terminate to omxdriver")
            self.omx.terminate(reason)
            self.end('killed',' end without waiting for omxplayer to finish') # end without waiting
        else:
            self.mon.log(self,"terminate, omxdriver not running")
            self.end('killed','terminate, mplayerdriver not running')


    def input_pressed(self,symbol):
        if symbol[0:4]=='omx-':
            self.control(symbol[4])
            
        elif symbol =='pause':
            self.pause()

        elif symbol=='stop':
            self.stop()
        else:
            pass


    def get_links(self):
        return self.track_params['links']

            
                
# ***************************************
# INTERNAL FUNCTIONS
# ***************************************

    # respond to normal stop
    def stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,">stop received")
        self.quit_signal=True


    #toggle pause
    def pause(self):
        if self.play_state in (VideoPlayer._PLAYING,VideoPlayer._ENDING):
            self.omx.pause()
            return True
        #NIK
        # for pausing video after first frame
        elif self.play_state == VideoPlayer._STARTING:
            self.omx.delayed_pause = True
            return True
        #NIK
        else:
            self.mon.log(self,"!<pause rejected")
            return False
        
    # other control when playing
    def control(self,char):
        if self.play_state==VideoPlayer._PLAYING and char not in ('q'):
            self.mon.log(self,"> send control to omx: "+ char)
            self.omx.control(char)
            return True
        else:
            self.mon.log(self,"!<control rejected")
            return False



# ***********************
# sequencing
# **********************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the omx process is not running, omx process can be initiated
         - _starting - omx process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - omx is doing its termination, controls cannot be sent
    """

    def init_play_state_machine(self):
        self.quit_signal=False
        self.play_state=VideoPlayer._CLOSED
 
    def start_play_state_machine(self,track):
        #initialise all the state machine variables
        #self.iteration = 0                             # for debugging
        self.quit_signal=False     # signal that user has pressed stop
        self.play_state=VideoPlayer._STARTING
        
        #play the selected track
        options=self.omx_audio+ " " + self.omx_volume + ' ' + self.omx_window + ' ' + self.show_params['omx-other-options']+" "
        # NIK ADDITION
        # adding subtitles file for video
        if 'omx-subtitles' in self.track_params and self.track_params['omx-subtitles'] <> '':
            subtitles_full_path = self.complete_path(self.track_params['omx-subtitles'])
            if os.path.exists (subtitles_full_path):
                options += '--font-size 40 --subtitles "' + subtitles_full_path + '" '

        if 'omx-subtitles-numlines' in self.track_params and self.track_params['omx-subtitles-numlines'] <> '':
            options += '--lines ' + self.track_params['omx-subtitles-numlines'] + ' '
        # END NIK ADDITION
        self.omx.play(track,options)
        self.mon.log (self,'Playing track from show Id: '+ str(self.show_id))
        # and start polling for state changes
        self.tick_timer=self.canvas.after(50, self.play_state_machine)
 

    def play_state_machine(self):      
        if self.play_state == VideoPlayer._CLOSED:
            self.mon.log(self,"      State machine: " + self.play_state)
            return 
                
        elif self.play_state == VideoPlayer._STARTING:
            self.mon.log(self,"      State machine: " + self.play_state)
            
            # if omxplayer is playing the track change to play state
            if self.omx.start_play_signal==True:
                self.mon.log(self,"            <start play signal received from omx")
                self.omx.start_play_signal=False
                self.play_state=VideoPlayer._PLAYING
                self.mon.log(self,"      State machine: omx_playing started")

            self.tick_timer=self.canvas.after(50, self.play_state_machine)

        elif self.play_state == VideoPlayer._PLAYING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # service any queued stop signals
            if self.quit_signal==True:
                self.mon.log(self,"      Service stop required signal")
                self.stop_omx()
                self.quit_signal=False
                # self.play_state = VideoPlayer._ENDING
                
            # omxplayer reports it is terminating so change to ending state
            if self.omx.end_play_signal:                    
                self.mon.log(self,"            <end play signal received")
                self.mon.log(self,"            <end detected at: " + str(self.omx.video_position))
                if self.omx.end_play_reason<>'nice_day':
                    # deal with omxplayer not sending 'have a nice day'
                    self.mon.warn(self,"            <end detected at: " + str(self.omx.video_position))
                    self.mon.warn(self,"            <pexpect reports: "+self.omx.end_play_reason)
                    self.mon.warn(self,'pexpect.before  is'+self.omx.xbefore)
                self.play_state = VideoPlayer._ENDING
                self.ending_count=0
                
            self.tick_timer=self.canvas.after(200, self.play_state_machine)

        elif self.play_state == VideoPlayer._ENDING:
            self.mon.log(self,"      State machine: " + self.play_state)
            # if spawned process has closed can change to closed state
            self.mon.log (self,"      State machine : is omx process running? -  "  + str(self.omx.is_running()))
            if self.omx.is_running() ==False:
                self.mon.log(self,"            <omx process is dead")
                self.play_state = VideoPlayer._CLOSED
                self.end('normal','quit by user or system')
            else:
                self.ending_count+=1
                if self.ending_count>10:
                    # deal with omxplayer not terminating at the end of a track
                    self.mon.warn(self,"            <omxplayer failed to close at: " + str(self.omx.video_position))
                    self.mon.warn(self,'pexpect.before  is'+self.omx.xbefore)
                    self.omx.kill()
                    self.mon.warn(self,'omxplayer now  terminated ')
                    self.play_state = VideoPlayer._CLOSED
                    self.end('normal','end from omxplayer failed to terminate')
                else:
                    self.tick_timer=self.canvas.after(200, self.play_state_machine)

    def stop_omx(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,"         >stop omx received from state machine")
        if self.play_state==VideoPlayer._PLAYING:
            self.omx.stop()
            return True
        else:
            self.mon.log(self,"!<stop rejected")
            return False




# *****************
# ending the player
# *****************

    def end(self,reason,message):

            # stop the plugin
            if self.track_params['plugin']<>'':
                self.pim.stop_plugin()

            # os.system("xrefresh -display :0")
            # abort the timer
            if self.tick_timer<>None:
                self.canvas.after_cancel(self.tick_timer)
                self.tick_timer=None
            
            if reason in ('error','killed'):
                self.end_callback(reason,message)
                self=None

            else:
                # normal end so do show control and animation

                # Control concurrent shows at end
                reason,message=self.show_manager.show_control(self.track_params['show-control-end'])
                if reason =='error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                   # clear events list for this track
                    if self.track_params['animate-clear']=='yes':
                        self.ppio.clear_events_list(id(self))
                    
                    # create animation events for ending
                    reason,message=self.ppio.animate(self.animate_end_text,id(self))
                    if reason=='error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        self.end_callback('normal',"track has terminated or quit")
                        self=None



# *****************
# displaying things
# *****************
    def display_content(self):

        #background colour
        if  self.background_colour<>'':   
           self.canvas.config(bg=self.background_colour)
            
        # delete previous content
        self.canvas.delete('pp-content')

        # background image
        if self.background_file<>'':
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(self,"Video background file not found: "+ self.background_img_file)
                self.end('error',"Video background file not found")
            else:
                pil_background_img=PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                             int(self.canvas['height'])/2,
                                             image=self.background,
                                            anchor=CENTER,
                                            tag='pp-content')

        # execute the plugin if required
        if self.track_params['plugin']<>'':

            reason,message,self.track = self.pim.do_plugin(self.track,self.track_params['plugin'],)
            if reason <> 'normal':
                return reason,message

                          
        # display show text if enabled
        if self.show_params['show-text']<> '' and self.track_params['display-show-text']=='yes':
            self.canvas.create_text(int(self.show_params['show-text-x']),int(self.show_params['show-text-y']),
                                                    anchor=NW,
                                                  text=self.show_params['show-text'],
                                                  fill=self.show_params['show-text-colour'],
                                                  font=self.show_params['show-text-font'],
                                                  tag='pp-content')


        # display track text if enabled
        if self.track_params['track-text']<> '':
            self.canvas.create_text(int(self.track_params['track-text-x']),int(self.track_params['track-text-y']),
                                                    anchor=NW,
                                                  text=self.track_params['track-text'],
                                                  fill=self.track_params['track-text-colour'],
                                                  font=self.track_params['track-text-font'],
                                                  tag='pp-content')

        # display instructions if enabled
        if self.enable_menu== True:
            self.canvas.create_text(int(self.show_params['hint-x']),
                                                    int(self.show_params['hint-y']),
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                font=self.show_params['hint-font'],
                                                anchor=NW,
                                                tag='pp-content')

        self.canvas.tag_raise('pp-click-area')
        self.canvas.update_idletasks( )
        return 'normal',''


# ****************
# utilities
# *****************

    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Background image is "+ track_file)
        return track_file

# original _
# warp _ or xy2


    def parse_window(self,line):
        
            fields = line.split()
            # check there is a command field
            if len(fields) < 1:
                    return 'error','no type field','',False,0,0,0,0
                
            # deal with original which has 1
            if fields[0]=='original':
                if len(fields) <> 1:
                        return 'error','number of fields for original','',False,0,0,0,0    
                return 'normal','',fields[0],False,0,0,0,0


            #deal with warp which has 1 or 5  arguments
            # check basic syntax
            if  fields[0] <>'warp':
                    return 'error','not a valid type','',False,0,0,0,0
            if len(fields) not in (1,5):
                    return 'error','wrong number of coordinates for warp','',False,0,0,0,0

            # deal with window coordinates    
            if len(fields) == 5:
                #window is specified
                if not (fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit() and fields[4].isdigit()):
                    return 'error','coordinates are not positive integers','',False,0,0,0,0
                has_window=True
                return 'normal','',fields[0],has_window,int(fields[1]),int(fields[2]),int(fields[3]),int(fields[4])
            else:
                # fullscreen
                has_window=True
                return 'normal','',fields[0],has_window,0,0,self.canvas['width'],self.canvas['height']
Example #42
0
class PPIO:
    """
    PPIO provides some IO facilties for Pi presents
     - configures GPIO pins from data in gpio.cfg
     - reads and debounces inputs pins, provides callbacks on state changes which are used to trigger mediashows
     - for output pins allows players to put events, which request the change of state of pins, into a queue. Events are executed at the required time.
    """

    # constants for buttons

    # cofiguration from gpio.cfg
    PIN = 0  # pin on RPi board GPIO connector e.g. P1-11
    DIRECTION = 1  # IN/OUT/NONE (None is not used)
    NAME = 2  # name for output
    RISING_NAME = 3  # name for rising edge callback
    FALLING_NAME = 4  # name ofr falling edge callback
    ONE_NAME = 5  # name for one state callback
    ZERO_NAME = 6  # name for zero state callback
    REPEAT = 7  #reperat interval for state callbacks (mS)
    THRESHOLD = 8  # threshold of debounce count for state change to be considered
    PULL = 9  # pull up or down or none
    # dynamic data
    COUNT = 10  # variable - count of the number of times the input has been 0 (limited to threshold)
    PRESSED = 11  # variable - debounced state
    LAST = 12  # varible - last state - used to detect edge
    REPEAT_COUNT = 13

    TEMPLATE = [
        '',  #pin
        '',  # direction
        '',  #name
        '',
        '',
        '',
        '',  #input names
        0,  # repeat
        0,  #threshold
        '',  #pull
        0,
        False,
        False,
        0
    ]  #dynamics

    PINLIST = ('P1-03', 'P1-05', 'P1-07', 'P1-08', 'P1-10', 'P1-11', 'P1-12',
               'P1-13', 'P1-15', 'P1-16', 'P1-18', 'P1-19', 'P1-21', 'P1-22',
               'P1-23', 'P1-24', 'P1-26')

    # index of shutdown pin
    SHUTDOWN_INDEX = 0

    # constants for sequencer

    SEQUENCER_PIN = 0  # GPIO pin number, the xx in P1-xx
    SEQUENCER_TO_STATE = 1  # False = off , True =on
    SEQUENCER_TIME = 2  # time since the epoch in seconds
    SEQUENCER_TAG = 3  # tag used to delete all matching event, usually a track reference.

    # CLASS VARIABLES
    events = []
    pins = []
    last_poll_time = 0
    options = None
    # gpio_enabled=False

    EVENT_TEMPLATE = [0, False, 0, None]

    #executed by main program and by each object using gpio
    def __init__(self):
        self.mon = Monitor()
        self.mon.on()
        self.options = command_options()

    # executed once from main program
    def init(self,
             pp_dir,
             pp_home,
             pp_profile,
             widget,
             button_tick,
             callback=None):

        # instantiate arguments
        self.widget = widget
        self.pp_dir = pp_dir
        self.pp_profile = pp_profile
        self.pp_home = pp_home
        self.button_tick = button_tick
        self.callback = callback

        PPIO.SHUTDOWN_INDEX = 0

        # read gpio.cfg file.
        if self.read(self.pp_dir, self.pp_home, self.pp_profile) == False:
            return False

        import RPi.GPIO as GPIO
        self.GPIO = GPIO

        #construct the GPIO control list from the configuration
        for index, pin_def in enumerate(PPIO.PINLIST):
            pin = copy.deepcopy(PPIO.TEMPLATE)
            pin_bits = pin_def.split('-')
            pin_num = pin_bits[1:]
            pin[PPIO.PIN] = int(pin_num[0])
            if self.config.has_section(pin_def) == False:
                self.mon.log(self, "no pin definition for " + pin_def)
                pin[PPIO.DIRECTION] = 'None'
            else:
                # unused pin
                if self.config.get(pin_def, 'direction') == 'none':
                    pin[PPIO.DIRECTION] = 'none'
                else:
                    pin[PPIO.DIRECTION] = self.config.get(pin_def, 'direction')
                    if pin[PPIO.DIRECTION] == 'in':
                        # input pin
                        pin[PPIO.RISING_NAME] = self.config.get(
                            pin_def, 'rising-name')
                        pin[PPIO.FALLING_NAME] = self.config.get(
                            pin_def, 'falling-name')
                        pin[PPIO.ONE_NAME] = self.config.get(
                            pin_def, 'one-name')
                        pin[PPIO.ZERO_NAME] = self.config.get(
                            pin_def, 'zero-name')
                        if pin[PPIO.FALLING_NAME] == 'pp-shutdown':
                            PPIO.SHUTDOWN_INDEX = index
                        if self.config.get(pin_def, 'repeat') <> '':
                            pin[PPIO.REPEAT] = int(
                                self.config.get(pin_def, 'repeat'))
                        else:
                            pin[PPIO.REPEAT] = -1
                        pin[PPIO.THRESHOLD] = int(
                            self.config.get(pin_def, 'threshold'))
                        if self.config.get(pin_def, 'pull-up-down') == 'up':
                            pin[PPIO.PULL] = GPIO.PUD_UP
                        elif self.config.get(pin_def,
                                             'pull-up-down') == 'down':
                            pin[PPIO.PULL] = GPIO.PUD_DOWN
                        else:
                            pin[PPIO.PULL] = GPIO.PUD_OFF
                    else:
                        # output pin
                        pin[PPIO.NAME] = self.config.get(pin_def, 'name')

            # print pin
            PPIO.pins.append(copy.deepcopy(pin))

        # setup GPIO
        self.GPIO.setwarnings(False)
        self.GPIO.setmode(self.GPIO.BOARD)

        # set up the GPIO inputs and outputs
        for index, pin in enumerate(PPIO.pins):
            num = pin[PPIO.PIN]
            if pin[PPIO.DIRECTION] == 'in':
                self.GPIO.setup(num, self.GPIO.IN, pull_up_down=pin[PPIO.PULL])
            elif pin[PPIO.DIRECTION] == 'out':
                self.GPIO.setup(num, self.GPIO.OUT)
                self.GPIO.setup(num, False)
        self.reset_inputs()
        PPIO.gpio_enabled = True

        #init timer
        self.button_tick_timer = None
        PPIO.last_scheduler_time = long(time.time())
        return True

    # called by main program only
    def poll(self):
        # look at the buttons
        self.do_buttons()

        # kick off output pin sequencer
        poll_time = long(time.time())
        # is current time greater than last time the sceduler was run (previous second or more)
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        while PPIO.last_scheduler_time <= poll_time:
            self.do_sequencer(PPIO.last_scheduler_time)
            PPIO.last_scheduler_time += 1

        # and loop
        self.button_tick_timer = self.widget.after(self.button_tick, self.poll)

# called by main program only

    def terminate(self):
        if self.button_tick_timer <> None:
            self.widget.after_cancel(self.button_tick_timer)
        self.clear_events_list(None)
        self.reset_outputs()
        self.GPIO.cleanup()

# ************************************************
# gpio input functions
# called by main program only
# ************************************************

    def reset_inputs(self):
        for pin in PPIO.pins:
            pin[PPIO.COUNT] = 0
            pin[PPIO.PRESSED] == False
            pin[PPIO.LAST] == False
            pin[PPIO.REPEAT_COUNT] = pin[PPIO.REPEAT]

    # index is of the pins array, provided by the callback ***** needs to be name
    def shutdown_pressed(self):
        if PPIO.SHUTDOWN_INDEX <> 0:
            return PPIO.pins[PPIO.SHUTDOWN_INDEX][PPIO.PRESSED]
        else:
            return False

    def do_buttons(self):
        for index, pin in enumerate(PPIO.pins):
            if pin[PPIO.DIRECTION] == 'in':
                # debounce
                if self.GPIO.input(pin[PPIO.PIN]) == 0:
                    if pin[PPIO.COUNT] < pin[PPIO.THRESHOLD]:
                        pin[PPIO.COUNT] += 1
                        if pin[PPIO.COUNT] == pin[PPIO.THRESHOLD]:
                            pin[PPIO.PRESSED] = True
                else:  # input us 1
                    if pin[PPIO.COUNT] > 0:
                        pin[PPIO.COUNT] -= 1
                        if pin[PPIO.COUNT] == 0:
                            pin[PPIO.PRESSED] = False

                #detect edges
                # falling edge
                if pin[PPIO.PRESSED] == True and pin[PPIO.LAST] == False:
                    pin[PPIO.LAST] = pin[PPIO.PRESSED]
                    pin[PPIO.REPEAT_COUNT] = pin[PPIO.REPEAT]
                    if pin[PPIO.FALLING_NAME] <> '' and self.callback <> None:
                        self.callback(index, pin[PPIO.FALLING_NAME], "falling")
            #rising edge
                if pin[PPIO.PRESSED] == False and pin[PPIO.LAST] == True:
                    pin[PPIO.LAST] = pin[PPIO.PRESSED]
                    pin[PPIO.REPEAT_COUNT] = pin[PPIO.REPEAT]
                    if pin[PPIO.RISING_NAME] <> '' and self.callback <> None:
                        self.callback(index, pin[PPIO.RISING_NAME], "rising")

                # do state callbacks
                if pin[PPIO.REPEAT_COUNT] == 0:
                    if pin[PPIO.ZERO_NAME] <> '' and pin[
                            PPIO.PRESSED] == True and self.callback <> None:
                        self.callback(index, pin[PPIO.ZERO_NAME], "zero")
                    if pin[PPIO.ONE_NAME] <> '' and pin[
                            PPIO.PRESSED] == False and self.callback <> None:
                        self.callback(index, pin[PPIO.ONE_NAME], "zero")
                    pin[PPIO.REPEAT_COUNT] = pin[PPIO.REPEAT]
                else:
                    if pin[PPIO.REPEAT] <> -1:
                        pin[PPIO.REPEAT_COUNT] -= 1

# ************************************************
# gpio output sequencer functions
# ************************************************

# execute events at the appropriate time and remove from list (runs from main program only)
# runs through list a number of times because of problems with pop messing up list

    def do_sequencer(self, schedule_time):
        # print 'sequencer run for: ' + str(schedule_time) + ' at ' + str(long(time.time()))
        while True:
            event_found = False
            for index, item in enumerate(PPIO.events):
                if item[PPIO.SEQUENCER_TIME] <= schedule_time:
                    event = PPIO.events.pop(index)
                    event_found = True
                    self.do_event(event[PPIO.SEQUENCER_PIN],
                                  event[PPIO.SEQUENCER_TO_STATE],
                                  item[PPIO.SEQUENCER_TIME])
                    break
            if event_found == False: break

    # execute an event
    def do_event(self, pin, to_state, req_time):
        self.mon.log(
            self,
            'pin P1-' + str(pin) + ' set  ' + str(to_state) + ' required: ' +
            str(req_time) + ' actual: ' + str(long(time.time())))
        # print 'pin P1-'+ str(pin)+ ' set  '+ str(to_state) + ' required: ' + str(req_time)+ ' actual: ' + str(long(time.time()))
        self.GPIO.output(pin, to_state)

# ************************************************
# gpio output sequencer interface methods
# these can be called from many classes so need to operate on class variables
# ************************************************

    def animate(self, text, tag):
        if self.options['gpio'] == True:
            lines = text.split("\n")
            for line in lines:
                error_text = self.parse_animate_fields(line, tag)
                if error_text <> '':
                    return 'error', error_text
            return 'normal', ''
        return 'normal', ''

    # clear event list
    def clear_events_list(self, tag):
        if self.options['gpio'] == True:
            self.mon.log(self, 'clear events list ')
            # empty event list
            if tag == None:
                PPIO.events = []
            else:
                self.remove_events(tag)

    def reset_outputs(self):
        if self.options['gpio'] == True:
            self.mon.log(self, 'reset outputs')
            for index, pin in enumerate(PPIO.pins):
                num = pin[PPIO.PIN]
                if pin[PPIO.DIRECTION] == 'out':
                    self.GPIO.output(num, False)

# ************************************************
# internal functions
# these can be called from many classes so need to operate on class variables
# ************************************************

    def parse_animate_fields(self, line, tag):
        fields = line.split()
        if len(fields) == 0:
            return ''

        name = fields[0]
        pin = self.pin_of(name)
        if pin == -1:
            return 'Unknown gpio logical output in: ' + line

        to_state_text = fields[1]
        if not (to_state_text in ('on', 'off')):
            return 'Illegal to-state in : ' + line

        if to_state_text == 'on':
            to_state = True
        else:
            to_state = False

        if len(fields) == 2:
            delay_text = '0'
        else:
            delay_text = fields[2]

        if not delay_text.isdigit():
            return 'Delay is not an integer in : ' + line
        delay = int(delay_text)

        self.add_event(pin, to_state, delay, tag)
        # self.print_events()
        return ''

    def pin_of(self, name):
        for pin in PPIO.pins:
            # print " in list" + pin[PPIO.NAME] + str(pin[PPIO.PIN] )
            if pin[PPIO.NAME] == name and pin[PPIO.DIRECTION] == 'out':
                return pin[PPIO.PIN]
        return -1

    def print_events(self):
        print
        for i in PPIO.events:
            print i

    def add_event(self, sequencer_pin, sequencer_to_state, sequencer_time,
                  sequencer_tag):
        poll_time = long(time.time())
        # delay is 0 so just do it, don't queue it.
        #if sequencer_time == 0:
        #print "firing now",poll_time
        #self.do_event(sequencer_pin,sequencer_to_state,poll_time)
        #return
        # prepare the event
        event = PPIO.EVENT_TEMPLATE
        event[PPIO.SEQUENCER_PIN] = sequencer_pin
        event[PPIO.SEQUENCER_TO_STATE] = sequencer_to_state
        event[PPIO.SEQUENCER_TIME] = sequencer_time + poll_time + 1
        event[PPIO.SEQUENCER_TAG] = sequencer_tag
        # print event
        # find the place in the events list and insert
        # first item in the list is earliest, if two have the same time then last to be added is fired last.
        abs_time = sequencer_time + poll_time
        copy_event = copy.deepcopy(event)
        for index, item in enumerate(PPIO.events):
            if abs_time < item[PPIO.SEQUENCER_TIME]:
                PPIO.events.insert(index, copy_event)
                return copy_event
        PPIO.events.append(copy_event)
        return copy_event

    # remove an event not used and does not work
    def remove_event(self, event):
        for index, item in enumerate(PPIO.events):
            if event == item:
                del PPIO.events[index]
                return True
        return False

    # remove all the events with the same tag, usually a track reference
    def remove_events(self, tag):
        left = []
        for item in PPIO.events:
            if tag <> item[PPIO.SEQUENCER_TAG]:
                left.append(item)
        PPIO.events = left
        #self.print_events()

# ***********************************
# reading gpio.cfg functions
# ************************************

    def read(self, pp_dir, pp_home, pp_profile):
        # try inside profile
        tryfile = pp_profile + os.sep + "gpio.cfg"
        # self.mon.log(self,"Trying gpio.cfg in profile at: "+ tryfile)
        if os.path.exists(tryfile):
            filename = tryfile
        else:
            # try inside pp_home
            # self.mon.log(self,"gpio.cfg not found at "+ tryfile+ " trying pp_home")
            tryfile = pp_home + os.sep + "gpio.cfg"
            if os.path.exists(tryfile):
                filename = tryfile
            else:
                # try inside pipresents
                # self.mon.log(self,"gpio.cfg not found at "+ tryfile + " trying inside pipresents")
                tryfile = pp_dir + os.sep + 'pp_home' + os.sep + "gpio.cfg"
                if os.path.exists(tryfile):
                    filename = tryfile
                else:
                    self.mon.log(self, "gpio.cfg not found at " + tryfile)
                    self.mon.err(self, "gpio.cfg not found")
                    return False
        self.config = ConfigParser.ConfigParser()
        self.config.read(filename)
        self.mon.log(self, "gpio.cfg read from " + filename)
        return True
Example #43
0
class BrowserPlayer:


    #state constants
    _CLOSED = "player_closed"    #probably will not exist
    _STARTING = "player_starting"  #uzbl beinf loaded and fifo created
    _WAITING = "wait for timeout" # waiting for browser to appear on the screen
    _PLAYING = "player_playing"  #track is playing to the screen
    _ENDING = "player_ending"  #track is in the process of ending due to quit or duration exceeded




# ***************************************
# EXTERNAL COMMANDS
# ***************************************

    def __init__(self,
                        show_id,
                         root,
                        canvas,
                        show_params,
                        track_params,
                     pp_dir,
                        pp_home,
                        pp_profile):

        self.mon=Monitor()
        self.mon.on()

        
        #instantiate arguments
        self.show_id=show_id
        self.root=root,
        self.canvas = canvas
        self.show_params=show_params  
        self.track_params=track_params
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # get duration limit (secs ) from profile
        if self.track_params['duration']<>"":
            self.duration= int(self.track_params['duration'])
        else:
            self.duration= int(self.show_params['duration'])
        self.duration_limit=20*self.duration


        # get background image from profile.
        self.background_file=''
        if self.track_params['background-image']<>"":
            self.background_file= self.track_params['background-image']
        else:
            if self.track_params['display-show-background']=='yes':
                self.background_file= self.show_params['background-image']
            
        # get background colour from profile.
        if self.track_params['background-colour']<>"":
            self.background_colour= self.track_params['background-colour']
        else:
            self.background_colour= self.show_params['background-colour']

        #get animation instructions from profile
        self.animate_begin_text=self.track_params['animate-begin']
        self.animate_end_text=self.track_params['animate-end']

        # open the plugin Manager
        self.pim=PluginManager(self.show_id,self.root,self.canvas,self.show_params,self.track_params,self.pp_dir,self.pp_home,self.pp_profile) 

        #create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()

        # could put instance generation in play, not sure which is better.
        self.bplayer=uzblDriver(self.canvas)
        self.command_timer=None
        self.tick_timer=None
        self.init_play_state_machine()



    def play(self, track,
                     showlist,
                     end_callback,
                     ready_callback,
                     enable_menu=False):

        #instantiate arguments
        self.track=track
        self.showlist=showlist
        self.end_callback=end_callback         # callback when finished
        self.ready_callback=ready_callback   #callback when ready to play
        self.enable_menu=enable_menu

        # callback to the calling object to e.g remove egg timer.
        if self.ready_callback<>None:
            self.ready_callback()

        # create an  instance of showmanager so we can control concurrent shows
        self.show_manager=ShowManager(self.show_id,self.showlist,self.show_params,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)

        #web window                  
        if self.track_params['web-window']<>'':
            self.web_window= self.track_params['web-window']
        else:
            self.web_window= self.show_params['web-window']

        reason,message,command,has_window,x1,y1,x2,y2=self.parse_window(self.web_window)
        if reason =='error':
            self.mon.err(self,'web window error: '+'  ' + message + ' in ' + self.web_window)
            self.end_callback(reason,message)
            self=None
        else:
            #deal with web_window
            if has_window==False:
                self.geometry = ' --geometry=maximized '
            else:
                width=x2-x1
                height=y2-y1
                self.geometry = "--geometry=%dx%d%+d%+d "  % (width,height,x1,y1)

            # get browser commands
            reason,message=self.parse_commands(self.track_params['browser-commands'])
            if reason != 'normal':
                self.mon.err(self,message)
                self.end_callback(reason,message)
                self=None
            else:
            
                # Control other shows at beginning
                reason,message=self.show_manager.show_control(self.track_params['show-control-begin'])
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                    #display content
                    reason,message=self.display_content()
                    if reason == 'error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        # create animation events
                        reason,message=self.ppio.animate(self.animate_begin_text,id(self))
                        if reason=='error':
                            self.mon.err(self,message)
                            self.end_callback(reason,message)
                            self=None
                        else:     
                            # start playing the track.
                            self.start_play_state_machine()

    def terminate(self,reason):
        """
        terminate the  player in special circumstances
        normal user termination if by key_pressed 'stop'
        reason will be killed or error
        """
        # circumvents state machine to terminate lower level and then itself.
        if self.bplayer<>None:
            self.mon.log(self,"sent terminate to uzbldriver")
            self.bplayer.terminate(reason)
            self.end('killed',' end without waiting for uzbl to finish') # end without waiting
        else:
            self.mon.log(self,"terminate, uzbldriver not running")
            self.end('killed','terminate, uzbldriver not running')

    def get_links(self):
        return self.track_params['links']

    def input_pressed(self,symbol):
        # print symbol, symbol[0:5]
        if symbol[0:5]=='uzbl-':
            self.control(symbol[5:])
            
        elif symbol == 'pause':
            self.pause()

        elif symbol=='stop':
            self.stop()


        
# ***************************************
# INTERNAL FUNCTIONS
# ***************************************

    #browser do not do pause
    def pause(self):
        self.mon.log(self,"!<pause rejected")
        return False
        
    # other control when playing, not currently used
    def control(self,char):
        if self.play_state==BrowserPlayer._PLAYING and char not in ('exit'):
            self.mon.log(self,"> send control to uzbl:"+ char)
            self.bplayer.control(char)
            return True
        else:
            self.mon.log(self,"!<control rejected")
            return False

    # respond to normal stop
    def stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,">stop received")
        self.quit_signal=True

         
      
# ***************************************
#  sequencing
# ***************************************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the mplayer process is not running, mplayer process can be initiated
         - _starting - mplayer process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - mplayer is doing its termination, controls cannot be sent
    """

    def init_play_state_machine(self):
        self.quit_signal=False
        self.play_state=BrowserPlayer._CLOSED
 
    def start_play_state_machine(self):
        #initialise all the state machine variables
        self.duration_count = 0
        self.quit_signal=False     # signal that user has pressed stop

        #play the track
        self.bplayer.play(self.track,self.geometry)
        self.mon.log (self,'Playing track from show Id: '+ str(self.show_id))
        self.play_state=BrowserPlayer._STARTING
        
        # and start polling for state changes and count duration
        self.tick_timer=self.canvas.after(50, self.play_state_machine)


    def play_state_machine(self):

        if self.play_state == BrowserPlayer._CLOSED:
            self.mon.log(self,"      State machine: " + self.play_state)
            return 
                
        elif self.play_state == BrowserPlayer._STARTING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            
            # if uzbl fifo is available can send comands to uzbl but change to wait state to wait for it to appear on screen
            if self.bplayer.start_play_signal==True:
                self.mon.log(self,"            <fifo available signal received from uzbl")
                self.bplayer.start_play_signal=False
                self.play_state=BrowserPlayer._WAITING
                # get rid of status bar
                self.bplayer.control('set show_status = 0')
                # and get ready to wait for browser to appear
                self.wait_count= 50   # 10 seconds at 200mS steps 
                self.mon.log(self,"      State machine: uzbl process alive")
                
            self.tick_timer=self.canvas.after(200, self.play_state_machine)


        elif self.play_state == BrowserPlayer._WAITING:
            if self.wait_count==0:
                # set state to playing
                self.play_state = BrowserPlayer._PLAYING
                # and start executing the browser commands
                self.play_commands()
                self.mon.log(self,"      State machine: uzbl_playing started")
                
            self.wait_count -=1
            self.tick_timer=self.canvas.after(200, self.play_state_machine)

        elif self.play_state == BrowserPlayer._PLAYING:
            self.duration_count+=1
            # self.mon.log(self,"      State machine: " + self.play_state)
            
            # service any queued stop signals and test duration count
            if self.quit_signal==True or (self.duration_limit>0 and self.duration_count>self.duration_limit):
                self.mon.log(self,"      Service stop required signal or timeout")
                # self.quit_signal=False
                self.stop_bplayer()
                self.play_state = BrowserPlayer._ENDING

            # uzbl reports it is terminating so change to ending state
            if self.bplayer.end_play_signal:                    
                self.mon.log(self,"            <end play signal received")
                self.play_state = BrowserPlayer._ENDING
            self.tick_timer=self.canvas.after(50, self.play_state_machine)

        elif self.play_state == BrowserPlayer._ENDING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # if spawned process has closed can change to closed state
            # self.mon.log (self,"      State machine : is luakit process running? -  "  + str(self.bplayer.is_running()))
            if self.bplayer.is_running() ==False:
                self.mon.log(self,"            <uzbl process is dead")
                if self.quit_signal==True:
                    self.quit_signal=False
                self.play_state = BrowserPlayer._CLOSED
                self.end('normal','quit required or timeout')
            else:
                self.tick_timer=self.canvas.after(50, self.play_state_machine)
                


    def stop_bplayer(self):
        # send signal to stop the track to the state machine
        self.mon.log(self,"         >send stop to uzbl driver")
        if self.play_state==BrowserPlayer._PLAYING:
            self.bplayer.stop()
            return True
        else:
            self.mon.log(self,"!<stop rejected")
            return False

# *****************
# ending the player
# *****************

    def end(self,reason,message):
            # stop the plugin
            if self.pim<>None:
                self.pim.stop_plugin()

            # abort the timers
            if self.tick_timer<>None:
                self.canvas.after_cancel(self.tick_timer)
                self.tick_timer=None
            if self.command_timer<>None:
                self.canvas.after_cancel(self.command_timer)
                self.tick_timer=None
            # clean up and fifos and sockets left by uzbl
            os.system('rm -f  /tmp/uzbl_*')
            if reason in ('error','killed'):
                self.end_callback(reason,message)
                self=None

            else:
                # normal end so do show control and animation

                # Control concurrent shows at end
                reason,message=self.show_manager.show_control(self.track_params['show-control-end'])
                if reason =='error':
                    self.mon.err(self,message)
                    self.end_callback(reason,message)
                    self=None
                else:
                   # clear events list for this track
                    if self.track_params['animate-clear']=='yes':
                        self.ppio.clear_events_list(id(self))
                    
                    # create animation events for ending
                    reason,message=self.ppio.animate(self.animate_end_text,id(self))
                    if reason=='error':
                        self.mon.err(self,message)
                        self.end_callback(reason,message)
                        self=None
                    else:
                        self.end_callback('normal',"track has terminated or quit")
                        self=None



# *****************
# displaying things
# *****************
            
    def display_content(self):
       

        self.canvas.delete('pp-content')


        #background colour
        if  self.background_colour<>'':
            self.canvas.config(bg=self.background_colour)
            
        if self.background_file<>'':
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(self,"Audio background file not found: "+ self.background_img_file)
                self.end('error',"Audio background file not found")
            else:
                pil_background_img=PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                              image=self.background,
                                              anchor=CENTER,
                                                tag='pp-content')

        # execute the plugin if required
        if self.track_params['plugin']<>'':

            reason,message,self.track = self.pim.do_plugin(self.track,self.track_params['plugin'],)
            if reason <> 'normal':
                return reason,message

                
        # display hint text if enabled
       
        if self.enable_menu== True:
            self.canvas.create_text(int(self.show_params['hint-x']),
                                                    int(self.show_params['hint-y']),
                                                  text=self.show_params['hint-text'],
                                                  fill=self.show_params['hint-colour'],
                                                font=self.show_params['hint-font'],
                                                    anchor=NW,
                                                tag='pp-content')

            
        # display show text if enabled
        if self.show_params['show-text']<> ''and self.track_params['display-show-text']=='yes':
            self.canvas.create_text(int(self.show_params['show-text-x']),int(self.show_params['show-text-y']),
                                                    anchor=NW,
                                                  text=self.show_params['show-text'],
                                                  fill=self.show_params['show-text-colour'],
                                                  font=self.show_params['show-text-font'],
                                                tag='pp-content')
            
        # display track text if enabled
        if self.track_params['track-text']<> '':
            self.canvas.create_text(int(self.track_params['track-text-x']),int(self.track_params['track-text-y']),
                                                    anchor=NW,
                                                  text=self.track_params['track-text'],
                                                  fill=self.track_params['track-text-colour'],
                                                  font=self.track_params['track-text-font'],
                                                tag='pp-content')

        self.mon.log(self,"Displayed background and text ")

        self.canvas.tag_raise('pp-click-area')
        
        self.canvas.update_idletasks( )

        return 'normal',''


     
# *******************   
# browser commands
# ***********************

    def parse_commands(self,command_text):
        self.command_list=[]
        lines = command_text.split('\n')
        for line in lines:
            if line.strip()=="":
                continue
            reason,entry=self.parse_command(line)
            if reason != 'normal':
                return 'error',entry
            self.command_list.append(copy.deepcopy(entry))
        # print self.command_list
        return 'normal',''

    def parse_command(self,line):
        fields = line.split()
        if fields[0]=='uzbl':
            # print fields[0], line[4:]
            return  'normal',[fields[0],line[4:]]
        
        if len(fields) not in (1,2):
            return 'error',"incorrect number of fields in command: " + line
        command=fields[0]
        arg=''
        if command not in ('load','refresh','wait','exit','loop'):
            return 'error','unknown command: '+ command
            
        if command in ('refresh','exit','loop') and len(fields)<>1:
            return 'error','incorrect number of fields for '+ command + 'in: ' + line
            
        if command == 'load':
            if len(fields)<>2:
                return 'error','incorrect number of fields for '+ command + 'in: ' + line
            else:
                arg = fields[1]


        if command == 'wait':
            if len(fields)<>2:
                return 'error','incorrect number of fields for '+ command + 'in: ' + line
            else:
                arg = fields[1]
                if not arg.isdigit():return 'error','Argument for Wait is not 0 or positive number in: ' + line

        return 'normal',[command,arg]


        
    def play_commands(self):
        if len(self.command_list)==0:
            return
        self.loop=0
        self.command_index=0
        self.canvas.after(100,self.execute_command)

        
    def execute_command(self):
        entry=self.command_list[self.command_index]
        command=entry[0]
        arg=entry[1]
        if self.command_index==len(self.command_list)-1:
            self.command_index=self.loop
        else:
            self.command_index+=1
            
        # execute command
        if command == 'load':
            #self.canvas.focus_force()
            #self.root.lower()
            file=self.complete_path(arg)
            self.bplayer.control('uri '+ file)
            self.command_timer=self.canvas.after(10,self.execute_command)
        elif command == 'refresh':
            self.bplayer.control('reload_ign_cache')
            self.command_timer=self.canvas.after(10,self.execute_command)
        elif command == 'wait':
            self.command_timer=self.canvas.after(1000*int(arg),self.execute_command)        
        elif  command=='exit':
            self.quit_signal=True
        elif command=='loop':
            self.loop=self.command_index
            self.command_timer=self.canvas.after(10,self.execute_command)
        elif command=='uzbl':
            self.bplayer.control(arg)
            self.command_timer=self.canvas.after(10,self.execute_command)

        
        
        
# *****************
# utilities
# *****************

    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Background image is "+ track_file)
        return track_file     
            





    def parse_window(self,line):
            # parses warp _ or xy2
            
            fields = line.split()
            # check there is a command field
            if len(fields) < 1:
                    return 'error','no type field','',False,0,0,0,0
                

            #deal with warp which has 1 or 5  arguments
            # check basic syntax
            if  fields[0] <>'warp':
                    return 'error','not a valid type','',False,0,0,0,0
            if len(fields) not in (1,5):
                    return 'error','wrong number of coordinates for warp','',False,0,0,0,0

            # deal with window coordinates    
            if len(fields) == 5:
                #window is specified
                if not (fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit() and fields[4].isdigit()):
                    return 'error','coordinates are not positive integers','',False,0,0,0,0
                has_window=True
                return 'normal','',fields[0],has_window,int(fields[1]),int(fields[2]),int(fields[3]),int(fields[4])
            else:
                # fullscreen
                has_window=False
                return 'normal','',fields[0],has_window,0,0,0,0
Example #44
0
class AudioPlayer:
    """       
            plays an audio track using mplayer against a coloured backgroud and image
            track can be paused and interrupted
            See pp_imageplayer for common software design description
    """

    # state constants
    _CLOSED = "mplayer_closed"  # probably will not exist
    _STARTING = "mplayer_starting"  # track is being prepared
    _PLAYING = "mplayer_playing"  # track is playing to the screen, may be paused
    _ENDING = "mplayer_ending"  # track is in the process of ending due to quit or end of track
    _WAITING = "wait for timeout"  # track has finished but timeout still running

    # audio mixer matrix settings
    _LEFT = "channels=2:1:0:0:1:1"
    _RIGHT = "channels=2:1:0:1:1:0"
    _STEREO = "channels=2"

    # ***************************************
    # EXTERNAL COMMANDS
    # ***************************************

    def __init__(self, show_id, root, canvas, show_params, track_params, pp_dir, pp_home, pp_profile):

        self.mon = Monitor()
        self.mon.off()

        # instantiate arguments
        self.show_id = show_id
        self.root = root
        self.canvas = canvas
        self.show_params = show_params
        self.track_params = track_params
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # get duration limit (secs ) from profile
        if self.track_params["duration"] <> "":
            self.duration = int(self.track_params["duration"])
            self.duration_limit = 20 * self.duration
        else:
            self.duration_limit = -1

        # get background image from profile.
        self.background_file = ""
        if self.track_params["background-image"] <> "":
            self.background_file = self.track_params["background-image"]
        else:
            if self.track_params["display-show-background"] == "yes":
                self.background_file = self.show_params["background-image"]

        # get background colour from profile.
        if self.track_params["background-colour"] <> "":
            self.background_colour = self.track_params["background-colour"]
        else:
            self.background_colour = self.show_params["background-colour"]

        # get audio device from profile.
        if self.track_params["mplayer-audio"] <> "":
            self.mplayer_audio = self.track_params["mplayer-audio"]
        else:
            self.mplayer_audio = self.show_params["mplayer-audio"]

        # get audio volume from profile.
        if self.track_params["mplayer-volume"] <> "":
            self.mplayer_volume = self.track_params["mplayer-volume"].strip()
        else:
            self.mplayer_volume = self.show_params["mplayer-volume"].strip()
        self.volume_option = "volume=" + self.mplayer_volume

        # get speaker from profile
        if self.track_params["audio-speaker"] <> "":
            self.audio_speaker = self.track_params["audio-speaker"]
        else:
            self.audio_speaker = self.show_params["audio-speaker"]

        if self.audio_speaker == "left":
            self.speaker_option = AudioPlayer._LEFT
        elif self.audio_speaker == "right":
            self.speaker_option = AudioPlayer._RIGHT
        else:
            self.speaker_option = AudioPlayer._STEREO

        # get animation instructions from profile
        self.animate_begin_text = self.track_params["animate-begin"]
        self.animate_end_text = self.track_params["animate-end"]

        # open the plugin Manager
        self.pim = PluginManager(
            self.show_id,
            self.root,
            self.canvas,
            self.show_params,
            self.track_params,
            self.pp_dir,
            self.pp_home,
            self.pp_profile,
        )

        # create an instance of PPIO so we can create gpio events
        self.ppio = PPIO()

        # could put instance generation in play, not sure which is better.
        self.mplayer = mplayerDriver(self.canvas)
        self.tick_timer = None
        self.init_play_state_machine()

    def play(self, track, showlist, end_callback, ready_callback, enable_menu=False):

        # instantiate arguments
        self.track = track
        self.showlist = showlist
        self.end_callback = end_callback  # callback when finished
        self.ready_callback = ready_callback  # callback when ready to play
        self.enable_menu = enable_menu

        # select the sound device
        if self.mplayer_audio <> "":
            if self.mplayer_audio == "hdmi":
                os.system("amixer -q -c 0 cset numid=3 2")
            else:
                os.system("amixer -q -c 0 cset numid=3 1")

        # callback to the calling object to e.g remove egg timer.
        if self.ready_callback <> None:
            self.ready_callback()

        # create an  instance of showmanager so we can control concurrent shows
        self.show_manager = ShowManager(
            self.show_id,
            self.showlist,
            self.show_params,
            self.root,
            self.canvas,
            self.pp_dir,
            self.pp_profile,
            self.pp_home,
        )

        # Control other shows at beginning
        reason, message = self.show_manager.show_control(self.track_params["show-control-begin"])
        if reason == "error":
            self.mon.err(self, message)
            self.end_callback(reason, message)
            self = None
        else:
            # display image and text
            reason, message = self.display_content()
            if reason == "error":
                self.mon.err(self, message)
                self.end_callback(reason, message)
                self = None
            else:
                # create animation events
                reason, message = self.ppio.animate(self.animate_begin_text, id(self))
                if reason == "error":
                    self.mon.err(self, message)
                    self.end_callback(reason, message)
                    self = None
                else:
                    # start playing the track.
                    if self.duration_limit <> 0:
                        self.start_play_state_machine()
                    else:
                        self.tick_timer = self.canvas.after(10, self.end_zero)

    def end_zero(self):
        self.end("normal", "zero duration")

    def terminate(self, reason):
        """
        terminate the  player in special circumstances
        normal user termination if by key_pressed 'stop'
        reason will be killed or error
        """
        # circumvents state machine to terminate lower level and then itself.
        if self.mplayer <> None:
            self.mon.log(self, "sent terminate to mplayerdriver")
            self.mplayer.terminate(reason)
            self.end("killed", " end without waiting for mplayer to finish")  # end without waiting
        else:
            self.mon.log(self, "terminate, mplayerdriver not running")
            self.end("killed", "terminate, mplayerdriver not running")

    def get_links(self):
        return self.track_params["links"]

    def input_pressed(self, symbol):
        if symbol[0:6] == "mplay-":
            self.control(symbol[6])

        elif symbol == "pause":
            self.pause()

        elif symbol == "stop":
            self.stop()

    # ***************************************
    # INTERNAL FUNCTIONS
    # ***************************************

    # toggle pause
    def pause(self):
        if self.play_state in (AudioPlayer._PLAYING, AudioPlayer._ENDING) and self.track <> "":
            self.mplayer.pause()
            return True
        else:
            self.mon.log(self, "!<pause rejected")
            return False

    # other control when playing, not currently used
    def control(self, char):
        if self.play_state == AudioPlayer._PLAYING and self.track <> "" and char not in ("q"):
            self.mon.log(self, "> send control to mplayer: " + char)
            self.mplayer.control(char)
            return True
        else:
            self.mon.log(self, "!<control rejected")
            return False

    # respond to normal stop
    def stop(self):
        # send signal to stop the track to the state machine
        self.mon.log(self, ">stop received")
        self.quit_signal = True

    # ***************************************
    #  sequencing
    # ***************************************

    """self. play_state controls the playing sequence, it has the following values.
         I am not entirely sure the starting and ending states are required.
         - _closed - the mplayer process is not running, mplayer process can be initiated
         - _starting - mplayer process is running but is not yet able to receive controls
         - _playing - playing a track, controls can be sent
         - _ending - mplayer is doing its termination, controls cannot be sent
    """

    def init_play_state_machine(self):
        self.quit_signal = False
        self.play_state = AudioPlayer._CLOSED

    def start_play_state_machine(self):
        # initialise all the state machine variables
        self.duration_count = 0
        self.quit_signal = False  # signal that user has pressed stop

        # play the track
        options = (
            self.show_params["mplayer-other-options"] + "-af " + self.speaker_option + "," + self.volume_option + " "
        )
        if self.track <> "":
            self.mplayer.play(self.track, options)
            self.mon.log(self, "Playing track from show Id: " + str(self.show_id))
            self.play_state = AudioPlayer._STARTING
        else:
            self.play_state = AudioPlayer._PLAYING
        # and start polling for state changes and count duration
        self.tick_timer = self.canvas.after(50, self.play_state_machine)

    def play_state_machine(self):
        self.duration_count += 1

        if self.play_state == AudioPlayer._CLOSED:
            self.mon.log(self, "      State machine: " + self.play_state)
            return

        elif self.play_state == AudioPlayer._STARTING:
            self.mon.log(self, "      State machine: " + self.play_state)

            # if mplayer is playing the track change to play state
            if self.mplayer.start_play_signal == True:
                self.mon.log(self, "            <start play signal received from mplayer")
                self.mplayer.start_play_signal = False
                self.play_state = AudioPlayer._PLAYING
                self.mon.log(self, "      State machine: mplayer_playing started")
            self.tick_timer = self.canvas.after(50, self.play_state_machine)

        elif self.play_state == AudioPlayer._PLAYING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # service any queued stop signals
            if self.quit_signal == True or (self.duration_limit > 0 and self.duration_count > self.duration_limit):
                self.mon.log(self, "      Service stop required signal or timeout")
                # self.quit_signal=False
                if self.track <> "":
                    self.stop_mplayer()
                    self.play_state = AudioPlayer._ENDING
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self.end("normal", "stop required signal or timeout")

            # mplayer reports it is terminating so change to ending state
            if self.track <> "" and self.mplayer.end_play_signal:
                self.mon.log(self, "            <end play signal received")
                self.mon.log(self, "            <end detected at: " + str(self.mplayer.audio_position))
                self.play_state = AudioPlayer._ENDING
            self.tick_timer = self.canvas.after(50, self.play_state_machine)

        elif self.play_state == AudioPlayer._ENDING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            # if spawned process has closed can change to closed state
            # self.mon.log (self,"      State machine : is mplayer process running? -  "  + str(self.mplayer.is_running()))
            if self.mplayer.is_running() == False:
                self.mon.log(self, "            <mplayer process is dead")
                if self.quit_signal == True:
                    self.quit_signal = False
                    self.play_state = AudioPlayer._CLOSED
                    self.end("normal", "quit required or timeout")
                elif self.duration_limit > 0 and self.duration_count < self.duration_limit:
                    self.play_state = AudioPlayer._WAITING
                    self.tick_timer = self.canvas.after(50, self.play_state_machine)
                else:
                    self.play_state = AudioPlayer._CLOSED
                    self.end("normal", "mplayer dead")
            else:
                self.tick_timer = self.canvas.after(50, self.play_state_machine)

        elif self.play_state == AudioPlayer._WAITING:
            # self.mon.log(self,"      State machine: " + self.play_state)
            if self.quit_signal == True or (self.duration_limit > 0 and self.duration_count > self.duration_limit):
                self.mon.log(self, "      Service stop required signal or timeout from wait")
                self.quit_signal = False
                self.play_state = AudioPlayer._CLOSED
                self.end("normal", "mplayer dead")
            else:
                self.tick_timer = self.canvas.after(50, self.play_state_machine)

    def stop_mplayer(self):
        # send signal to stop the track to the state machine
        self.mon.log(self, "         >stop mplayer received from state machine")
        if self.play_state == AudioPlayer._PLAYING:
            self.mplayer.stop()
            return True
        else:
            self.mon.log(self, "!<stop rejected")
            return False

    # *****************
    # ending the player
    # *****************

    def end(self, reason, message):
        # stop the plugin
        if self.track_params["plugin"] <> "":
            self.pim.stop_plugin()

        # abort the timer
        if self.tick_timer <> None:
            self.canvas.after_cancel(self.tick_timer)
            self.tick_timer = None

        if reason in ("error", "killed"):
            self.end_callback(reason, message)
            self = None

        else:
            # normal end so do show control and animation

            # Control concurrent shows at end
            reason, message = self.show_manager.show_control(self.track_params["show-control-end"])
            if reason == "error":
                self.mon.err(self, message)
                self.end_callback(reason, message)
                self = None
            else:
                # clear events list for this track
                if self.track_params["animate-clear"] == "yes":
                    self.ppio.clear_events_list(id(self))

                # create animation events for ending
                reason, message = self.ppio.animate(self.animate_end_text, id(self))
                if reason == "error":
                    self.mon.err(self, message)
                    self.end_callback(reason, message)
                    self = None
                else:
                    self.end_callback("normal", "track has terminated or quit")
                    self = None

    # *****************
    # displaying things
    # *****************

    def display_content(self):

        # if self.background_file<>'' or self.show_params['show-text']<> '' or #self.track_params['track-text']<> '' or self.enable_menu== True or #self.track_params['clear-screen']=='yes':

        if self.track_params["clear-screen"] == "yes":
            self.canvas.delete("pp-content")
            # self.canvas.update()

        # background colour
        if self.background_colour <> "":
            self.canvas.config(bg=self.background_colour)

        if self.background_file <> "":
            self.background_img_file = self.complete_path(self.background_file)
            if not os.path.exists(self.background_img_file):
                self.mon.err(self, "Audio background file not found: " + self.background_img_file)
                self.end("error", "Audio background file not found")
            else:
                pil_background_img = PIL.Image.open(self.background_img_file)
                self.background = PIL.ImageTk.PhotoImage(pil_background_img)
                self.drawn = self.canvas.create_image(
                    int(self.canvas["width"]) / 2,
                    int(self.canvas["height"]) / 2,
                    image=self.background,
                    anchor=CENTER,
                    tag="pp-content",
                )

        # execute the plugin if required
        if self.track_params["plugin"] <> "":

            reason, message, self.track = self.pim.do_plugin(self.track, self.track_params["plugin"])
            if reason <> "normal":
                return reason, message

        # display hint text if enabled

        if self.enable_menu == True:
            self.canvas.create_text(
                int(self.show_params["hint-x"]),
                int(self.show_params["hint-y"]),
                text=self.show_params["hint-text"],
                fill=self.show_params["hint-colour"],
                font=self.show_params["hint-font"],
                anchor=NW,
                tag="pp-content",
            )

        # display show text if enabled
        if self.show_params["show-text"] <> "" and self.track_params["display-show-text"] == "yes":
            self.canvas.create_text(
                int(self.show_params["show-text-x"]),
                int(self.show_params["show-text-y"]),
                anchor=NW,
                text=self.show_params["show-text"],
                fill=self.show_params["show-text-colour"],
                font=self.show_params["show-text-font"],
                tag="pp-content",
            )

        # display track text if enabled
        if self.track_params["track-text"] <> "":
            self.canvas.create_text(
                int(self.track_params["track-text-x"]),
                int(self.track_params["track-text-y"]),
                anchor=NW,
                text=self.track_params["track-text"],
                fill=self.track_params["track-text-colour"],
                font=self.track_params["track-text-font"],
                tag="pp-content",
            )

        self.mon.log(self, "Displayed background and text ")

        self.canvas.tag_raise("pp-click-area")

        self.canvas.update_idletasks()

        return "normal", ""

    # *****************
    # utilities
    # *****************

    def complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Background image is " + track_file)
        return track_file
Example #45
0
class PPEditor:



# ***************************************
# INIT
# ***************************************

    def __init__(self):
    
        self.editor_issue="1.2"

# get command options
        self.command_options=ed_options()

# get directory holding the code
        self.pp_dir=sys.path[0]
            
        if not os.path.exists(self.pp_dir+os.sep+"pp_editor.py"):
            tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit()
            
          
#Initialise logging
        Monitor.log_path=self.pp_dir
        self.mon=Monitor()
        self.mon.on()

        if self.command_options['debug'] == True:
            Monitor.global_enable=True
        else:
            Monitor.global_enable=False

        self.mon.log (self, "Pi Presents Editor is starting")

        self.mon.log (self," OS and separator " + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: code "+sys.path[0])


 # set up the gui
 
        #root is the Tkinter root widget
        self.root = tk.Tk()
        self.root.title("Editor for Pi Presents")

        # self.root.configure(background='grey')

        self.root.resizable(False,False)

        #define response to main window closing
        self.root.protocol ("WM_DELETE_WINDOW", self.app_exit)

        # bind some display fields
        self.filename = tk.StringVar()
        self.display_selected_track_title = tk.StringVar()
        self.display_show = tk.StringVar()


# define menu
        menubar = Menu(self.root)

        profilemenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        profilemenu.add_command(label='Open', command = self.open_existing_profile)
        profilemenu.add_command(label='Validate', command = self.validate_profile)
        menubar.add_cascade(label='Profile', menu = profilemenu)

        ptypemenu = Menu(profilemenu, tearoff=0, bg="grey", fg="black")
        ptypemenu.add_command(label='Exhibit', command = self.new_exhibit_profile)
        ptypemenu.add_command(label='Media Show', command = self.new_mediashow_profile)
        ptypemenu.add_command(label='Menu', command = self.new_menu_profile)
        ptypemenu.add_command(label='Presentation', command = self.new_presentation_profile)
        ptypemenu.add_command(label='Interactive', command = self.new_interactive_profile)
        ptypemenu.add_command(label='Live Show', command = self.new_liveshow_profile)
        ptypemenu.add_command(label='RadioButton Show', command = self.new_radiobuttonshow_profile)
        ptypemenu.add_command(label='Hyperlink Show', command = self.new_hyperlinkshow_profile)
        ptypemenu.add_command(label='Blank', command = self.new_blank_profile)
        profilemenu.add_cascade(label='New from Template', menu = ptypemenu)
        
        showmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        showmenu.add_command(label='Delete', command = self.remove_show)
        showmenu.add_command(label='Edit', command = self.m_edit_show)
        showmenu.add_command(label='Copy To', command = self.copy_show)
        menubar.add_cascade(label='Show', menu = showmenu)

        stypemenu = Menu(showmenu, tearoff=0, bg="grey", fg="black")
        stypemenu.add_command(label='Menu', command = self.add_menu)
        stypemenu.add_command(label='MediaShow', command = self.add_mediashow)
        stypemenu.add_command(label='LiveShow', command = self.add_liveshow)
        stypemenu.add_command(label='HyperlinkShow', command = self.add_hyperlinkshow)
        stypemenu.add_command(label='RadioButtonShow', command = self.add_radiobuttonshow)
        showmenu.add_cascade(label='Add', menu = stypemenu)
        
        medialistmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='MediaList', menu = medialistmenu)
        medialistmenu.add_command(label='Add', command = self.add_medialist)
        medialistmenu.add_command(label='Delete', command = self.remove_medialist)
      
        trackmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        trackmenu.add_command(label='Delete', command = self.remove_track)
        trackmenu.add_command(label='Edit', command = self.m_edit_track)
        trackmenu.add_command(label='Add from Dir', command = self.add_tracks_from_dir)
        trackmenu.add_command(label='Add from File', command = self.add_track_from_file)


        menubar.add_cascade(label='Track', menu = trackmenu)

        typemenu = Menu(trackmenu, tearoff=0, bg="grey", fg="black")
        typemenu.add_command(label='Video', command = self.new_video_track)
        typemenu.add_command(label='Audio', command = self.new_audio_track)
        typemenu.add_command(label='Image', command = self.new_image_track)
        typemenu.add_command(label='Web', command = self.new_web_track)
        typemenu.add_command(label='Message', command = self.new_message_track)
        typemenu.add_command(label='Show', command = self.new_show_track)
        typemenu.add_command(label='Menu Background', command = self.new_menu_background_track)
        typemenu.add_command(label='Child Show', command = self.new_child_show_track)
        trackmenu.add_cascade(label='New', menu = typemenu)

        toolsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Tools', menu = toolsmenu)
        toolsmenu.add_command(label='Update All', command = self.update_all)
        
        optionsmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Options', menu = optionsmenu)
        optionsmenu.add_command(label='Edit', command = self.edit_options)

        helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Help', menu = helpmenu)
        helpmenu.add_command(label='Help', command = self.show_help)
        helpmenu.add_command(label='About', command = self.about)
         
        self.root.config(menu=menubar)

        top_frame=Frame(self.root)
        top_frame.pack(side=TOP)
        bottom_frame=Frame(self.root)
        bottom_frame.pack(side=TOP, fill=BOTH, expand=1)        

        left_frame=Frame(bottom_frame, padx=5)
        left_frame.pack(side=LEFT)
        middle_frame=Frame(bottom_frame,padx=5)
        middle_frame.pack(side=LEFT)              
        right_frame=Frame(bottom_frame,padx=5,pady=10)
        right_frame.pack(side=LEFT)
        updown_frame=Frame(bottom_frame,padx=5)
        updown_frame.pack(side=LEFT)
        
        tracks_title_frame=Frame(right_frame)
        tracks_title_frame.pack(side=TOP)
        tracks_label = Label(tracks_title_frame, text="Tracks in Selected Medialist")
        tracks_label.pack()
        tracks_frame=Frame(right_frame)
        tracks_frame.pack(side=TOP)
        shows_title_frame=Frame(left_frame)
        shows_title_frame.pack(side=TOP)
        shows_label = Label(shows_title_frame, text="Shows")
        shows_label.pack()
        shows_frame=Frame(left_frame)
        shows_frame.pack(side=TOP)
        shows_title_frame=Frame(left_frame)
        shows_title_frame.pack(side=TOP)
        medialists_title_frame=Frame(left_frame)
        medialists_title_frame.pack(side=TOP)
        medialists_label = Label(medialists_title_frame, text="Medialists")
        medialists_label.pack()
        medialists_frame=Frame(left_frame)
        medialists_frame.pack(side=LEFT)
        
 # define buttons 

        add_button = Button(middle_frame, width = 5, height = 2, text='Edit\nShow',
                              fg='black', command = self.m_edit_show, bg="light grey")
        add_button.pack(side=RIGHT)
        
        add_button = Button(updown_frame, width = 5, height = 1, text='Add',
                              fg='black', command = self.add_track_from_file, bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame, width = 5, height = 1, text='Edit',
                              fg='black', command = self.m_edit_track, bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame, width = 5, height = 1, text='Up',
                              fg='black', command = self.move_track_up, bg="light grey")
        add_button.pack(side=TOP)
        add_button = Button(updown_frame, width = 5, height = 1, text='Down',
                              fg='black', command = self.move_track_down, bg="light grey")
        add_button.pack(side=TOP)



# define display of showlist 
        scrollbar = Scrollbar(shows_frame, orient=tk.VERTICAL)
        self.shows_display = Listbox(shows_frame, selectmode=SINGLE, height=7,
                                    width = 40, bg="white",activestyle=NONE,
                                    fg="black", yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.shows_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.shows_display.pack(side=LEFT, fill=BOTH, expand=1)
        self.shows_display.bind("<ButtonRelease-1>", self.e_select_show)

    
# define display of medialists
        scrollbar = Scrollbar(medialists_frame, orient=tk.VERTICAL)
        self.medialists_display = Listbox(medialists_frame, selectmode=SINGLE, height=7,
                                    width = 40, bg="white",activestyle=NONE,
                                    fg="black",yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.medialists_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.medialists_display.pack(side=LEFT,  fill=BOTH, expand=1)
        self.medialists_display.bind("<ButtonRelease-1>", self.select_medialist)


# define display of tracks
        scrollbar = Scrollbar(tracks_frame, orient=tk.VERTICAL)
        self.tracks_display = Listbox(tracks_frame, selectmode=SINGLE, height=15,
                                    width = 40, bg="white",activestyle=NONE,
                                    fg="black",yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.tracks_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.tracks_display.pack(side=LEFT,fill=BOTH, expand=1)
        self.tracks_display.bind("<ButtonRelease-1>", self.e_select_track)


# initialise editor options class
        self.options=Options(self.pp_dir) #creates options file in code directory if necessary
        
# initialise variables      
        self.init()
        
#and enter Tkinter event loop
        self.root.mainloop()        


# ***************************************
# INIT AND EXIT
# ***************************************
    def app_exit(self):
        self.root.destroy()
        exit()


    def init(self):
        self.options.read()
        self.pp_home_dir = self.options.pp_home_dir
        self.initial_media_dir = self.options.initial_media_dir
        self.mon.log(self,"Data Home from options is "+self.pp_home_dir)
        self.mon.log(self,"Initial Media from options is "+self.initial_media_dir)
        self.current_medialist=None
        self.current_showlist=None
        self.current_show=None
        self.shows_display.delete(0,END)
        self.medialists_display.delete(0,END)
        self.tracks_display.delete(0,END)



# ***************************************
# MISCELLANEOUS
# ***************************************

    def edit_options(self):
        """edit the options then read them from file"""
        eo = OptionsDialog(self.root, self.options.options_file,'Edit Options')
        if eo.result==True: self.init()


    def show_help (self):
        tkMessageBox.showinfo("Help",
       "Read 'manual.pdf'")
  

    def about (self):
        tkMessageBox.showinfo("About","Editor for Pi Presents Profiles\n"
                   +"For profile version: " + self.editor_issue + "\nAuthor: Ken Thompson"+
                              "\nWebsite: http://pipresents.wordpress.com/")

    def validate_profile(self):
        val =Validator()
        val.validate_profile(self.root,self.pp_dir,self.pp_home_dir,self.pp_profile_dir,self.editor_issue,True)


    
# **************
# PROFILES
# **************

    def open_existing_profile(self):
        initial_dir=self.pp_home_dir+os.sep+"pp_profiles"
        if os.path.exists(initial_dir)==False:
            self.mon.err(self,"Home directory not found: " + initial_dir + "\n\nHint: Data Home option must end in pp_home")
            return
        dir_path=tkFileDialog.askdirectory(initialdir=initial_dir)
        # dir_path="C:\Users\Ken\pp_home\pp_profiles\\ttt"
        if len(dir_path)>0:
            self.open_profile(dir_path)
        

    def open_profile(self,dir_path):
        showlist_file = dir_path + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file)==False:
            self.mon.err(self,"Not a Profile: " + dir_path + "\n\nHint: Have you opened the profile directory?")
            return
        self.pp_profile_dir = dir_path
        self.root.title("Editor for Pi Presents - "+ self.pp_profile_dir)
        if self.open_showlist(self.pp_profile_dir)==False:
            self.init()
            return
        self.open_medialists(self.pp_profile_dir)
        self.refresh_tracks_display()


    def new_profile(self,profile):
        d = Edit1Dialog(self.root,"New Profile","Name", "")
        if d .result == None:
            return
        name=str(d.result)
        if name=="":
            tkMessageBox.showwarning("New Profile","Name is blank")
            return
        to = self.pp_home_dir + os.sep + "pp_profiles"+ os.sep + name
        if os.path.exists(to)== True:
            tkMessageBox.showwarning( "New Profile","Profile exists\n(%s)" % to )
            return
        shutil.copytree(profile, to, symlinks=False, ignore=None)
        self.open_profile(to)


        
    def new_exhibit_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_exhibit"
        self.new_profile(profile)

    def new_interactive_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_interactive"
        self.new_profile(profile)

    def new_menu_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_menu"
        self.new_profile(profile)

    def new_presentation_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_presentation"
        self.new_profile(profile)

    def new_blank_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_blank"
        self.new_profile(profile)

    def new_mediashow_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_mediashow"
        self.new_profile(profile)
        
    def new_liveshow_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_liveshow"
        self.new_profile(profile)

    def new_radiobuttonshow_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_radiobuttonshow"
        self.new_profile(profile)

    def new_hyperlinkshow_profile(self):
        profile = self.pp_dir+"/pp_home/pp_profiles/ppt_hyperlinkshow"
        self.new_profile(profile)

# ***************************************
# Shows
# ***************************************

    def open_showlist(self,dir):
        showlist_file = dir + os.sep + "pp_showlist.json"
        if os.path.exists(showlist_file)==False:
            self.mon.err(self,"showlist file not found at " + dir + "\n\nHint: Have you opened the profile directory?")
            self.app_exit()
        self.current_showlist=ShowList()
        self.current_showlist.open_json(showlist_file)
        if float(self.current_showlist.sissue())<float(self.editor_issue) or  (self.command_options['forceupdate'] == True and float(self.current_showlist.sissue())==float(self.editor_issue)):
            self.update_profile()
            self.mon.err(self,"Version of profile has been updated to "+self.editor_issue+", please re-open")
            return False
        if float(self.current_showlist.sissue())>float(self.editor_issue):
            self.mon.err(self,"Version of profile is greater than editor, must exit")
            self.app_exit()
        self.refresh_shows_display()
        return True


    def save_showlist(self,dir):
        if self.current_showlist<>None:
            showlist_file = dir + os.sep + "pp_showlist.json"
            self.current_showlist.save_list(showlist_file)
            
    def add_eventshow(self):
        self.add_show(PPdefinitions.new_shows['eventshow'])

    def add_mediashow(self):
        self.add_show(PPdefinitions.new_shows['mediashow'])

    def add_liveshow(self):
        self.add_show(PPdefinitions.new_shows['liveshow'])

    def add_radiobuttonshow(self):
        self.add_show(PPdefinitions.new_shows['radiobuttonshow'])

    def add_hyperlinkshow(self):
        self.add_show(PPdefinitions.new_shows['hyperlinkshow'])
        
    def add_menu(self):
        self.add_show(PPdefinitions.new_shows['menu'])

    def add_start(self):  
        self.add_show(PPdefinitions.new_shows['start'])


    def add_show(self,default):
        # append it to the showlist and then add the medialist
        if self.current_showlist<>None:
            d = Edit1Dialog(self.root,"AddShow",
                                "Show Reference", "")
            if d.result == None:
                return
            name=str(d.result)
            if name=="":
                tkMessageBox.showwarning(
                                "Add Show",
                                "Name is blank"
                                        )
                return
            if self.current_showlist.index_of_show(name)<>-1:
                tkMessageBox.showwarning(
                                "Add Show",
                                "A Show with this name already exists"
                                        )
                return            
            copied_show=self.current_showlist.copy(default,name)
            mediafile=self.add_medialist(name)
            if mediafile<>'':
                copied_show['medialist']=mediafile
            self.current_showlist.append(copied_show)
            self.save_showlist(self.pp_profile_dir)
            self.refresh_shows_display()

            
    def remove_show(self):
        if  self.current_showlist<>None and self.current_showlist.length()>0 and self.current_showlist.show_is_selected():
            if tkMessageBox.askokcancel("Delete Show","Delete Show"):
                index= self.current_showlist.selected_show_index()
                self.current_showlist.remove(index)
                self.save_showlist(self.pp_profile_dir)
                self.refresh_shows_display()

    def show_refs(self):
        _show_refs=[]
        for index in range(self.current_showlist.length()):
            if self.current_showlist.show(index)['show-ref']<>"start":
                _show_refs.append(copy.deepcopy(self.current_showlist.show(index)['show-ref']))
        return _show_refs
 
    def refresh_shows_display(self):
        self.shows_display.delete(0,self.shows_display.size())
        for index in range(self.current_showlist.length()):
            self.shows_display.insert(END, self.current_showlist.show(index)['title']+"   ["+self.current_showlist.show(index)['show-ref']+"]")        
        if self.current_showlist.show_is_selected():
            self.shows_display.itemconfig(self.current_showlist.selected_show_index(),fg='red')            
            self.shows_display.see(self.current_showlist.selected_show_index())

            
    def e_select_show(self,event):
        if self.current_showlist<>None and self.current_showlist.length()>0:
            mouse_item_index=int(event.widget.curselection()[0])
            self.current_showlist.select(mouse_item_index)
            self.refresh_shows_display()

    def copy_show(self):
        if  self.current_showlist<>None and self.current_showlist.show_is_selected():
            self.add_show(self.current_showlist.selected_show())

        
    def m_edit_show(self):
        self.edit_show(PPdefinitions.show_types,PPdefinitions.show_field_specs)
        
     

    def edit_show(self,show_types,field_specs):
        if self.current_showlist<>None and self.current_showlist.show_is_selected():
            d=EditItem(self.root,"Edit Show",self.current_showlist.selected_show(),show_types,field_specs,self.show_refs(),
                       self.initial_media_dir,self.pp_home_dir,'show')
            if d.result == True:

                self.save_showlist(self.pp_profile_dir)
                self.refresh_shows_display()

 

# ***************************************
#   Medialists
# ***************************************

    def open_medialists(self,dir):
        self.medialists = []
        for file in os.listdir(dir):
            if file.endswith(".json") and file<>'pp_showlist.json':
                self.medialists = self.medialists + [file]
        self.medialists_display.delete(0,self.medialists_display.size())
        for index in range (len(self.medialists)):
            self.medialists_display.insert(END, self.medialists[index])
        self.current_medialists_index=-1
        self.current_medialist=None


    def add_medialist(self,name=None):
        if name==None:
            d = Edit1Dialog(self.root,"Add Medialist",
                                    "File", "")
            if d.result == None:
                return ''
            name=str(d.result)
            if name=="":
                tkMessageBox.showwarning(
                                    "Add medialist",
                                    "Name is blank"
                                            )
                return ''
            
        if not name.endswith(".json"):
            name=name+(".json")
                
        path = self.pp_profile_dir + os.sep + name
        if os.path.exists(path)== True:
                tkMessageBox.showwarning("Add medialist","Medialist file exists\n(%s)" % path)
                return ''
        nfile = open(path,'wb')
        nfile.write("{")
        nfile.write("\"issue\":  \""+self.editor_issue+"\",\n")
        nfile.write("\"tracks\": [")
        nfile.write("]")
        nfile.write("}")
        nfile.close()
        # append it to the list
        self.medialists.append(copy.deepcopy(name))
        # add title to medialists display
        self.medialists_display.insert(END, name)  
        # and set it as the selected medialist
        self.refresh_medialists_display()
        return name


    def remove_medialist(self):
        if self.current_medialist<>None:
            if tkMessageBox.askokcancel("Delete Medialist","Delete Medialist"):
                os.remove(self.pp_profile_dir+ os.sep + self.medialists[self.current_medialists_index])
                self.open_medialists(self.pp_profile_dir)
                self.refresh_medialists_display()
                self.refresh_tracks_display()


    def select_medialist(self,event):
        """
        user clicks on a medialst in a profile so try and select it.
        """
        # needs forgiving int for possible tkinter upgrade
        if len(self.medialists)>0:
            self.current_medialists_index=int(event.widget.curselection()[0])
            self.current_medialist=MediaList()
            if not self.current_medialist.open_list(self.pp_profile_dir+ os.sep + self.medialists[self.current_medialists_index],self.current_showlist.sissue()):
                self.mon.err(self,"medialist is a different version to showlist: "+ self.medialists[self.current_medialists_index])
                self.app_exit()        
            self.refresh_tracks_display()
            self.refresh_medialists_display()


    def refresh_medialists_display(self):
        self.medialists_display.delete(0,len(self.medialists))
        for index in range (len(self.medialists)):
            self.medialists_display.insert(END, self.medialists[index])
        if self.current_medialist<>None:
            self.medialists_display.itemconfig(self.current_medialists_index,fg='red')
            self.medialists_display.see(self.current_medialists_index)

    def save_medialist(self):
        basefile=self.medialists[self.current_medialists_index]
        #print type(basefile)
        # basefile=str(basefile)
        #print type(basefile)
        file = self.pp_profile_dir+ os.sep + basefile
        self.current_medialist.save_list(file)

          
# ***************************************
#   Tracks
# ***************************************
                
    def refresh_tracks_display(self):
        self.tracks_display.delete(0,self.tracks_display.size())
        if self.current_medialist<>None:
            for index in range(self.current_medialist.length()):
                if self.current_medialist.track(index)['track-ref']<>"":
                    track_ref_string="  ["+self.current_medialist.track(index)['track-ref']+"]"
                else:
                    track_ref_string=""
                self.tracks_display.insert(END, self.current_medialist.track(index)['title']+track_ref_string)        
            if self.current_medialist.track_is_selected():
                self.tracks_display.itemconfig(self.current_medialist.selected_track_index(),fg='red')            
                self.tracks_display.see(self.current_medialist.selected_track_index())
            
    def e_select_track(self,event):
        if self.current_medialist<>None and self.current_medialist.length()>0:
            mouse_item_index=int(event.widget.curselection()[0])
            self.current_medialist.select(mouse_item_index)
            self.refresh_tracks_display()

    def m_edit_track(self):
        self.edit_track(PPdefinitions.track_types,PPdefinitions.track_field_specs)

    def edit_track(self,track_types,field_specs):      
        if self.current_medialist<>None and self.current_medialist.track_is_selected():
            d=EditItem(self.root,"Edit Track",self.current_medialist.selected_track(),track_types,field_specs,self.show_refs(),
                       self.initial_media_dir,self.pp_home_dir,'track')
            if d.result == True:
                self.save_medialist()
            self.refresh_tracks_display()

    def move_track_up(self):
        if self.current_medialist<>None and self.current_medialist.track_is_selected():
            self.current_medialist.move_up()
            self.refresh_tracks_display()
            self.save_medialist()

    def move_track_down(self):
        if self.current_medialist<>None and self.current_medialist.track_is_selected():
            self.current_medialist.move_down()
            self.refresh_tracks_display()
            self.save_medialist()
        
    def new_track(self,fields,values):
        if self.current_medialist<>None:
            #print '\nfields ', fields
            #print '\nvalues ', values
            new_track=copy.deepcopy(fields)
            #print ',\new track ',new_track
            self.current_medialist.append(new_track)
            #print '\nbefore values ',self.current_medialist.print_list()
            if values<>None:
                self.current_medialist.update(self.current_medialist.length()-1,values)
            self.current_medialist.select(self.current_medialist.length()-1)
            self.refresh_tracks_display()
            self.save_medialist()

    def new_message_track(self):
        self.new_track(PPdefinitions.new_tracks['message'],None)
            
    def new_video_track(self):
        self.new_track(PPdefinitions.new_tracks['video'],None)
  
    def new_audio_track(self):
        self.new_track(PPdefinitions.new_tracks['audio'],None)

    def new_web_track(self):
        self.new_track(PPdefinitions.new_tracks['web'],None)
        
    def new_image_track(self):
        self.new_track(PPdefinitions.new_tracks['image'],None)

    def new_show_track(self):
        self.new_track(PPdefinitions.new_tracks['show'],None)
 
    def new_menu_background_track(self):
        self.new_track(PPdefinitions.new_tracks['menu-background'],None)

    def new_child_show_track(self):
        self.new_track(PPdefinitions.new_tracks['child-show'],None)

    def remove_track(self):
        if  self.current_medialist<>None and self.current_medialist.length()>0 and self.current_medialist.track_is_selected():
            if tkMessageBox.askokcancel("Delete Track","Delete Track"):
                index= self.current_medialist.selected_track_index()
                self.current_medialist.remove(index)
                self.save_medialist()
                self.refresh_tracks_display()
                
    def add_track_from_file(self):
        if self.current_medialist==None: return
        # print "initial directory ", self.options.initial_media_dir
        files_path=tkFileDialog.askopenfilename(initialdir=self.options.initial_media_dir, multiple=True)
        # fix for tkinter bug
        files_path =  self.root.tk.splitlist(files_path)
        for file_path in files_path:
            file_path=os.path.normpath(file_path)
            # print "file path ", file_path
            self.add_track(file_path)
        self.save_medialist()

    def add_tracks_from_dir(self):
        if self.current_medialist==None: return
        image_specs =[
            PPdefinitions.IMAGE_FILES,
            PPdefinitions.VIDEO_FILES,
            PPdefinitions.AUDIO_FILES,
            PPdefinitions.WEB_FILES,
          ('All files', '*')]    #last one is ignored in finding files
                                    # in directory, for dialog box only
        directory=tkFileDialog.askdirectory(initialdir=self.options.initial_media_dir)
        # deal with tuple returned on Cancel
        if len(directory)==0: return
        # make list of exts we recognise
        exts = []
        for image_spec in image_specs[:-1]:
            image_list=image_spec[1:]
            for ext in image_list:
                exts.append(copy.deepcopy(ext))
        for file in os.listdir(directory):
            (root_file,ext_file)= os.path.splitext(file)
            if ext_file.lower() in exts:
                file_path=directory+os.sep+file
                #print "file path before ", file_path
                file_path=os.path.normpath(file_path)
                #print "file path after ", file_path
                self.add_track(file_path)
        self.save_medialist()


    def add_track(self,afile):
        relpath = os.path.relpath(afile,self.pp_home_dir)
        #print "relative path ",relpath
        common = os.path.commonprefix([afile,self.pp_home_dir])
        #print "common ",common
        if common.endswith("pp_home") == False:
            location = afile
        else:
            location = "+" + os.sep + relpath
            location = string.replace(location,'\\','/')
            #print "location ",location
        (root,title)=os.path.split(afile)
        (root,ext)= os.path.splitext(afile)
        if ext.lower() in PPdefinitions.IMAGE_FILES:
            self.new_track(PPdefinitions.new_tracks['image'],{'title':title,'track-ref':'','location':location})
        elif ext.lower() in PPdefinitions.VIDEO_FILES:
            self.new_track(PPdefinitions.new_tracks['video'],{'title':title,'track-ref':'','location':location})
        elif ext.lower() in PPdefinitions.AUDIO_FILES:
            self.new_track(PPdefinitions.new_tracks['audio'],{'title':title,'track-ref':'','location':location})
        elif ext.lower() in PPdefinitions.WEB_FILES:
            self.new_track(PPdefinitions.new_tracks['web'],{'title':title,'track-ref':'','location':location})
        else:
            self.mon.err(self,afile + " - file extension not recognised")



# *********************************************
# update profile
# **********************************************

    def update_all(self):
        self.init()
        for profile_file in os.listdir(self.pp_home_dir+os.sep+'pp_profiles'):
            # self.mon.log (self,"Updating "+profile_file)
            self.pp_profile_dir = self.pp_home_dir+os.sep+'pp_profiles'+ os.sep + profile_file
            if not os.path.exists(self.pp_profile_dir+os.sep+"pp_showlist.json"):
                tkMessageBox.showwarning("Pi Presents","Not a profile, skipping "+self.pp_profile_dir)
            else:
                self.current_showlist=ShowList()
                #self.mon.log (self,"Checking version "+profile_file)
                self.current_showlist.open_json(self.pp_profile_dir+os.sep+"pp_showlist.json")
                if float(self.current_showlist.sissue())<float(self.editor_issue):
                    self.mon.log(self,"Version of profile "+profile_file+ "  is being updated to "+self.editor_issue)
                    self.update_profile()
                elif (self.command_options['forceupdate'] == True and float(self.current_showlist.sissue())==float(self.editor_issue)):
                    self.mon.log(self, "Forced updating of " + profile_file + ' to '+self.editor_issue)
                    self.update_profile()
                elif float(self.current_showlist.sissue())>float(self.editor_issue):
                    tkMessageBox.showwarning("Pi Presents", "Version of profile " +profile_file+ " is greater than editor, skipping")
                else:
                    self.mon.log(self," Profile " + profile_file + " Already up to date ")
        self.init()
        tkMessageBox.showwarning("Pi Presents","All profiles updated")
            
    def update_profile(self):
        #open showlist and update its shows
        # self.mon.log (self,"Updating show ")
        ifile  = open(self.pp_profile_dir + os.sep + "pp_showlist.json", 'rb')
        shows = json.load(ifile)['shows']
        ifile.close()
        replacement_shows=self.update_shows(shows)
        dic={'issue':self.editor_issue,'shows':replacement_shows}
        ofile  = open(self.pp_profile_dir + os.sep + "pp_showlist.json", "wb")
        json.dump(dic,ofile,sort_keys=True,indent=1)

        
        # UPDATE MEDIALISTS AND THEIR TRACKS
        for file in os.listdir(self.pp_profile_dir):
            if file.endswith(".json") and file<>'pp_showlist.json':
                # self.mon.log (self,"Updating medialist " + file)
                #open a medialist and update its tracks
                ifile  = open(self.pp_profile_dir + os.sep + file, 'rb')
                tracks = json.load(ifile)['tracks']
                ifile.close()
                replacement_tracks=self.update_tracks(tracks)
                dic={'issue':self.editor_issue,'tracks':replacement_tracks}
                ofile  = open(self.pp_profile_dir + os.sep + file, "wb")
                json.dump(dic,ofile,sort_keys=True,indent=1)


    def update_tracks(self,old_tracks):
        # get correct spec from type of field
        replacement_tracks=[]
        for old_track in old_tracks:
            #print '\nold track ',old_track
            track_type=old_track['type']
            spec_fields=PPdefinitions.new_tracks[track_type]
            left_overs=dict()
            # go through track and delete fields not in spec
            for key in old_track.keys():
                if key in spec_fields:
                        left_overs[key]=old_track[key]
            #print '\n leftovers',left_overs
            replacement_track=copy.deepcopy(PPdefinitions.new_tracks[track_type])
            #print '\n before update', replacement_track
            replacement_track.update(left_overs)
            #print '\nafter update',replacement_track
            replacement_tracks.append(copy.deepcopy(replacement_track))
        return replacement_tracks


    def update_shows(self,old_shows):
        # get correct spec from type of field
        replacement_shows=[]
        for old_show in old_shows:
            show_type=old_show['type']
            spec_fields=PPdefinitions.new_shows[show_type]
            left_overs=dict()
            # go through track and delete fields not in spec
            for key in old_show.keys():
                if key in spec_fields:
                        left_overs[key]=old_show[key]
            # print '\n leftovers',left_overs
            replacement_show=copy.deepcopy(PPdefinitions.new_shows[show_type])
            replacement_show.update(left_overs)
            replacement_shows.append(copy.deepcopy(replacement_show))
        return replacement_shows                
Example #46
0
class MenuShow:
    """ Displays a menu with optional hint below it. User can traverse the menu and
              select a track using key or button presses.
        Interface:
         * play - displays the menu and selects the first entry
         * input_pressed,  - receives user events passes them to a Player if a track is playing,
                otherwise actions them with _next, _previous, _play_selected_track, _end
         Optional display of eggtimer by means of Players ready_callback
         Supports imageplayer, videoplayer,messagplayer,audioplayer,menushow,mediashow
         Destroys itself on exit
    """

# *********************
# external interface
# ********************

    def __init__(self,
                            show_params,
                             root,
                            canvas,
                            showlist,
                             pp_dir,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the name of the configuration dictionary section for the menu
            showlist  - the showlist
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory"""
        
        self.mon=Monitor()
        self.mon.on()
        
        self.display_guidelines_command=show_params['menu-guidelines']
        self.display_guidelines=self.display_guidelines_command

        
        #instantiate arguments
        self.show_params=show_params
        self.root=root
        self.canvas=canvas
        self.showlist=showlist
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()
        
        # init variables
        self.drawn  = None
        self.player=None
        self.shower=None
        self.menu_timeout_running=None
        self.error=False




    def play(self,show_id,end_callback,ready_callback,top=False,command='nil'):
        """ displays the menu 
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """
        
        #instantiate arguments
        self.show_id=show_id
        self.end_callback=end_callback
        self.ready_callback=ready_callback
        self.top=top
        self.command=command

        # check  data files are available.
        self.menu_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.menu_file):
            self.mon.err(self,"Medialist file not found: "+ self.menu_file)
            self.end('error',"Medialist file not found")
        
        #create a medialist for the menu and read it.
        self.medialist=MediaList()
        if self.medialist.open_list(self.menu_file,self.showlist.sissue()) == False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self.end('error',"Version of medialist different to Pi Presents")

        #get control bindings for this show if top level
        controlsmanager=ControlsManager()
        if self.top==True:
            self.controls_list=controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list=controlsmanager.merge_show_controls(self.controls_list,self.show_params['controls'])

           
        if self.show_params['has-background']=="yes":
            background_index=self.medialist.index_of_track ('pp-menu-background')
            if background_index>=0:
                self.menu_img_file = self.complete_path(self.medialist.track(background_index)['location'])
                if not os.path.exists(self.menu_img_file):
                    self.mon.err(self,"Menu background file not found: "+ self.menu_img_file)
                    self.end('error',"Menu background file not found")
            else:
                self.mon.err(self,"Menu background not found in medialist")
                self.end('error',"Menu background not found")
                               
        self.end_menushow_signal= False
        if self.ready_callback<>None:
            self.ready_callback()

        self.menu_timeout_value=int(self.show_params['timeout'])*1000
        self.do_menu()


    def do_menu(self):
        #start timeout alarm if required
        if int(self.show_params['timeout'])<>0:
            self.menu_timeout_running=self.canvas.after(self.menu_timeout_value,self.timeout_menu)

        if self.show_params['menu-background-colour']<>'':
            self.canvas.config(bg=self.show_params['menu-background-colour'])
            
        self.canvas.delete('pp-content')
        self.canvas.update()
        
        # display background image
        if self.show_params['has-background']=="yes":
            self.display_background()

        self.delete_eggtimer()
        self.display_new_menu()
        self.canvas.tag_raise('pp-click-area')            
        self.canvas.update_idletasks( )

        # display menu text if enabled
        if self.show_params['menu-text']<> '':
            self.canvas.create_text(int(self.show_params['menu-text-x']),int(self.show_params['menu-text-y']),
                                                    anchor=NW,
                                                  text=self.show_params['menu-text'],
                                                  fill=self.show_params['menu-text-colour'],
                                                  font=self.show_params['menu-text-font'],
                                                  tag='pp-content')

        self.canvas.update_idletasks( )
        
        # display instructions (hint)
        hint_text=self.show_params['hint-text']
        if hint_text<>'':
            self.canvas.create_text(int(self.show_params['hint-x']),int(self.show_params['hint-y']),
                                                    anchor=NW,
                                text=hint_text,
                                fill=self.show_params['hint-colour'],
                                font=self.show_params['hint-font'],
                                tag='pp-content')

        self.canvas.update_idletasks( )


    #stop received from another concurrent show
    def managed_stop(self):
        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
        if self.shower<>None:
            self.shower.managed_stop()
        elif self.player<>None:
            self.end_menushow_signal=True
            self.player.input_pressed('stop')
        else:
            self.end('normal','stopped by ShowManager')
            

    # kill or error received
    def terminate(self,reason):
        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
        if self.shower<>None:
            self.shower.terminate(reason)
        elif self.player<>None:
            self.player.terminate(reason)
        else:
            self.end(reason,'Terminated no shower or player running')



   # respond to user inputs.
    def input_pressed(self,symbol,edge,source):
        self.mon.log(self,"Show Id: "+str(self.show_id)+" received key or operation: " + symbol)
        if self.show_params['disable-controls']=='yes':
            return 

        # if at top convert symbolic name to operation otherwise lower down we have received an operation
        # look through list of standard symbols to find match (symbolic-name, function name) operation =lookup (symbol
        if self.top==True:
            operation=self.lookup_control(symbol,self.controls_list)
        else:
            operation=symbol
            
        # print 'operation',operation
        # if no match for symbol against standard operations then return
        if operation=='':
            return
        else:
            if self.shower<>None:
                # if next lower show is running pass down operatin to  the show and lower levels
                self.shower.input_pressed(operation,source,edge) 
            else:
                #service the standard inputs for this show
                if operation=='stop':
                    if self.menu_timeout_running<>None:
                        self.canvas.after_cancel(self.menu_timeout_running)
                        self.menu_timeout_running=None
                    if self.shower<>None:
                        self.shower.input_pressed('stop',edge,source)
                    elif self.player<>None:
                        self.player.input_pressed('stop')
                    else:
                        # not at top so end the show
                        if  self.top == False:
                            self.end('normal',"exit from stop command")
                        else:
                            pass
              
                elif operation in ('up','down'):
                # if child or sub-show running and is a show pass down
                # if  child not running - move
                    if self.shower<>None:
                        self.shower.input_pressed(operation,edge,source)
                    else:
                        if self.player==None:
                            if self.menu_timeout_running<>None:
                                self.canvas.after_cancel(self.menu_timeout_running)
                                self.menu_timeout_running=self.canvas.after(self.menu_timeout_value,self.timeout_menu)
                            if operation=='up':
                                self.previous()
                            else:
                                self.next()
                        
                elif operation =='play':
                    # if child running and is show - pass down
                    # if no track already running  - play
                    if self.shower<>None:
                        self.shower.input_pressed(operation,edge,source)
                    else:
                        if self.player==None:
                            self.play_selected_track(self.medialist.selected_track())

                elif operation == 'pause':
                    # pass down if show or track running.
                    if self.shower<>None:
                        self.shower.input_pressed(operation,edge,source)
                    elif self.player<>None:
                        self.player.input_pressed(operation)
                        
                elif operation[0:4]=='omx-' or operation[0:6]=='mplay-'or operation[0:5]=='uzbl-':
                    if self.player<>None:
                        self.player.input_pressed(operation)


 
    def lookup_control(self,symbol,controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''


# *********************
# INTERNAL FUNCTIONS
# ********************

# *********************
# Sequencing
# *********************

    def timeout_menu(self):
        self.end('normal','menu timeout')
        return
        
    def next(self):     
        self.highlight_menu_entry(self.menu_index,False)
        self.medialist.next('ordered')
        if self.menu_index==self.menu_length-1:
            self.menu_index=0
        else:
            self.menu_index+=1
        self.highlight_menu_entry(self.menu_index,True)     


    def previous(self):   
        self.highlight_menu_entry(self.menu_index,False)
        if self.menu_index==0:
            self.menu_index=self.menu_length-1
        else:
            self.menu_index-=1
        self.medialist.previous('ordered')
        self.highlight_menu_entry(self.menu_index,True)
        

     # at the end of a track just re-display the menu with the original callback from the menu       
    def what_next(self,message):
        # user wants to end
        if self.end_menushow_signal==True:
            self.end_menushow_signal=False
            self.end('normal',"show ended by user")
        else:
            self.do_menu()


# *********************
# Dispatching to Players
# *********************

    def play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
         #remove menu and show working.....        

        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
            
        self.canvas.delete('pp-content')
        self.display_eggtimer(self.resource('menushow','m01'))
    
        # dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
        
        if track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track['location'])
            self.player=VideoPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.delete_eggtimer,
                                        enable_menu=False)
                                        
        elif track_type=="audio":
            # create a audioplayer
            track_file=self.complete_path(selected_track['location'])
            self.player=AudioPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.delete_eggtimer,
                                        enable_menu=False)
                                        
        elif track_type=="image":
            # images played from menus don't have children
            track_file=self.complete_path(selected_track['location'])
            self.player=ImagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                    self.showlist,
                                    self.end_player,
                                    self.delete_eggtimer,
                                    enable_menu=False,
                                    )
            
        elif track_type=="web":
            # create a browser
            track_file=self.complete_path(selected_track['location'])
            self.player=BrowserPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.delete_eggtimer,
                                        enable_menu=False)

                                    
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(text,
                                    self.showlist,
                                    self.end_player,
                                    self.delete_eggtimer,
                                    enable_menu=False
                                    )
 
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self.end("Unknown show")
          
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                                self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                               self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.delete_eggtimer,top=False,command='nil')

            elif selected_show['type']=="liveshow":    
                self.shower= LiveShow(selected_show,
                                                                 self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.delete_eggtimer,top=False,command='nil')

            elif selected_show['type']=="radiobuttonshow":
                self.shower= RadioButtonShow(selected_show,
                                                       self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                         self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="hyperlinkshow":
                self.shower= HyperlinkShow(selected_show,
                                                        self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')


            elif selected_show['type']=="menu": 
                self.shower= MenuShow(selected_show,
                                                        self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.delete_eggtimer,top=False,command='nil')                    
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self.end("Unknown show type")  
                
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self.end("Unknown track type")
    
    # callback from when player ends
    def end_player(self,reason,message):
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            self.display_eggtimer(self.resource('menushow','m02'))
            self.what_next(message)

    # callback from when shower ends
    def end_shower(self,show_id,reason,message):
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        if reason in ("killed","error"):
            self.end(reason,message)
        else:
            self.display_eggtimer(self.resource('menushow','m03'))
            self.what_next(message)  
   


# *********************
# Ending the show
# *********************
    # finish the player for killing, error or normally
    # this may be called directly if sub/child shows or players are not running
    # if they might be running then need to call terminate?????
    
    def end(self,reason,message):
        self.mon.log(self,"Ending menushow: "+ self.show_params['show-ref'])  
        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
        self.end_callback(self.show_id,reason,message)
        self=None
        return


# *********************
# Displaying things
# *********************

    def display_background(self):
        pil_menu_img=PIL.Image.open(self.menu_img_file)
        self.menu_background = PIL.ImageTk.PhotoImage(pil_menu_img)
        self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                      int(self.canvas['height'])/2,
                                      image=self.menu_background,
                                      anchor=CENTER,
                                      tag='pp-content')


   
    
    def display_new_menu(self):

        # calculate menu geometry
        error,reason=self.calculate_geometry()
        if error<>'normal':
            self.mon.err(self,"Menu geometry error: "+ reason)
            self.end('error',"Menu geometry error")
        else:
            # display the menu entries
            self.display_menu_entries()


    def display_menu_entries(self):
        # init the loop
        column_index=0
        row_index=0
        self.menu_length=1
        
        # id store is a list of elements each being a list of the three ids of the elements of the entry
        self.menu_entry_id=[]
        # offsets for the above
        self.icon_id_index=0 # rectangle around the icon
        self.image_id_index=1 # icon image - needed for tkinter
        self.text_id_index=2 # the text - need whn no icon is displayed

        #select the startof the medialist
        self.medialist.start()

        #loop through menu entries
        while True:
            #display the entry
            #calculate top left corner of entry
            self.calculate_entry_position(column_index,row_index)


            # display the button strip
            self.display_entry_strip()

            #display the selected entry highlight
            icon_id=self.display_icon_rectangle()

            #display the image in the icon
            image_id=self.display_icon_image()

            if self.show_params['menu-text-mode']<>'none':
                text_id=self.display_icon_text()
            else:
                text_id=None

            #append id's to the list
            self.menu_entry_id.append([icon_id,image_id,text_id])

            self.canvas.update_idletasks( )            
            #and loop
            if self.medialist.at_end():
                break
            self.menu_length+=1
            self.medialist.next('ordered')

            if self.direction=='horizontal':
                column_index+=1
                if column_index>=self.menu_columns:
                    column_index=0
                    row_index+=1
            else:
                row_index+=1
                if row_index>=self.menu_rows:
                    row_index=0
                    column_index+=1
                    
        # finally select and highlight the first entry
        self.medialist.start()
        self.menu_index=0
        self.highlight_menu_entry(self.menu_index,True)


    def print_geometry(self,total_width,total_height):
        print 'menu width: ', self.menu_width
        print 'columns', self.menu_columns
        print 'icon width: ', self.icon_width
        print 'horizontal padding: ', self.menu_horizontal_padding
        print 'text width: ', self.text_width
        print 'entry width: ', self.entry_width
        print 'total width: ', total_width
        print 'x separation: ', self.x_separation
        print ''
        print 'menu height', self.menu_height
        print 'rows: ', self.menu_rows
        print 'icon height', self.icon_height
        print 'vertical padding: ', self.menu_vertical_padding        
        print 'text height', self.text_height
        print 'entry height', self.entry_height
        print 'total height', total_height
        print 'y separation', self.y_separation

        
    # ------------------------------------------------------------------
    #calculate menu entry size and separation between menu entries
    # ------------------------------------------------------------------
    def calculate_geometry(self):

        self.display_strip=self.show_params['menu-strip']
        self.screen_width=int(self.canvas['width'])
        self.screen_height=int(self.canvas['height'])
        
        if self.display_strip=='yes':
            self.strip_padding=int(self.show_params['menu-strip-padding'])
        else:
            self.strip_padding=0

        # parse the menu window
        error,reason,self.menu_x_left,self.menu_y_top,self.menu_x_right,self.menu_y_bottom=self.parse_menu_window(self.show_params['menu-window'])
        if error<>'normal':
            return 'error',"Menu Window error: "+ reason

        if self.show_params['menu-icon-mode']=='none' and self.show_params['menu-text-mode']=='none':
            return 'error','Icon and Text are both None'
        if self.show_params['menu-icon-mode']=='none' and self.show_params['menu-text-mode']=='overlay':
            return 'error','cannot overlay none icon'

        self.direction=self.show_params['menu-direction']
        
        self.menu_width=self.menu_x_right - self.menu_x_left
        self.menu_height=self.menu_y_bottom - self.menu_y_top

        self.list_length=self.medialist.display_length()

        # get or calculate rows and columns
        if self.direction=='horizontal':
            if self.show_params['menu-columns']=='':
                return 'error','blank columns for horizontal direction'
            self.menu_columns=int(self.show_params['menu-columns'])
            self.menu_rows=self.list_length//self.menu_columns
            if self.list_length % self.menu_columns<>0:
                self.menu_rows+=1
        else:
            if self.show_params['menu-rows']=='':
                return 'error','blank rows for vertical direction'
            self.menu_rows=int(self.show_params['menu-rows'])
            self.menu_columns=self.list_length//self.menu_rows
            if self.list_length % self.menu_rows<>0:
                self.menu_columns+=1
                
        self.x_separation=int(self.show_params['menu-horizontal-separation'])
        self.y_separation=int(self.show_params['menu-vertical-separation'])

        # get size of padding depending on exitence of icon and text
        if self.show_params['menu-icon-mode'] in ('thumbnail','bullet') and self.show_params['menu-text-mode'] == 'right':
            self.menu_horizontal_padding=int(self.show_params['menu-horizontal-padding'])
        else:
            self.menu_horizontal_padding=0

        if self.show_params['menu-icon-mode'] in ('thumbnail','bullet') and self.show_params['menu-text-mode'] == 'below':
            self.menu_vertical_padding=int(self.show_params['menu-vertical-padding'])
        else:
            self.menu_vertical_padding=0
            
        #calculate size of icon depending on use
        if self.show_params['menu-icon-mode'] in ('thumbnail','bullet'):
            self.icon_width=int(self.show_params['menu-icon-width'])
            self.icon_height=int(self.show_params['menu-icon-height'])
        else:
            self.icon_width=0
            self.icon_height=0

        #calculate size of text box depending on mode
        if self.show_params['menu-text-mode']<>'none':
            self.text_width=int(self.show_params['menu-text-width'])
            self.text_height=int(self.show_params['menu-text-height'])
        else:
            self.text_width=0
            self.text_height=0
            
        # calculate size of entry box by combining text and icon sizes
        if self.show_params['menu-text-mode'] == 'right':
            self.entry_width=self.icon_width+self.menu_horizontal_padding+self.text_width
            self.entry_height=max(self.text_height,self.icon_height)
        elif self.show_params['menu-text-mode']=='below':
            self.entry_width=max(self.text_width,self.icon_width)
            self.entry_height=self.icon_height + self.menu_vertical_padding + self.text_height 
        else:
            # no text or overlaid text
            if self.show_params['menu-icon-mode'] in ('thumbnail','bullet'):
                # icon only
                self.entry_width=self.icon_width
                self.entry_height=self.icon_height
            else:
                #text only
                self.entry_width=self.text_width
                self.entry_height=self.text_height

        if self.entry_width<=self.menu_horizontal_padding:
            return 'error','entry width is zero'

        if self.entry_height<=self.menu_vertical_padding:
            return 'error','entry height is zero'

        # calculate totals for debugging puropses
        total_width=self.menu_columns * self.entry_width +(self.menu_columns-1)*self.x_separation
        total_height=self.menu_rows * self.entry_height + (self.menu_rows-1)*self.y_separation
        
        # self.print_geometry(total_width,total_height)   


        # display guidelines and debgging text if there is a problem     
        if total_width>self.menu_width and self.display_guidelines<>'never':
                self.display_guidelines='always'
                self.mon.log(self,'\nMENU IS WIDER THAN THE WINDOW')
                self.print_geometry(total_width,total_height)


        if total_height>self.menu_height and self.display_guidelines<>'never':
                self.display_guidelines='always'
                self.mon.log(self,'\nMENU IS TALLER THAN THE WINDOW')
                self.print_geometry(total_width,total_height)            

        # display calculated total rectangle guidelines for debugging
        if self.display_guidelines=='always':
            points=[self.menu_x_left,self.menu_y_top, self.menu_x_left+total_width,self.menu_y_top+total_height]

            # and display the icon rectangle
            self.canvas.create_rectangle(points,
                                           outline='red',
                                           fill='',
                                           tag='pp-content')

        
        # display menu rectangle guidelines for debugging
        if self.display_guidelines=='always':
            points=[self.menu_x_left,self.menu_y_top, self.menu_x_right,self.menu_y_bottom]
            self.canvas.create_rectangle(points,
                                           outline='blue',
                                           fill='',
                                           tag='pp-content')
                
        return 'normal',''

    def calculate_entry_position(self,column_index,row_index):
            self.entry_x=self.menu_x_left+ column_index*(self.x_separation+self.entry_width)
            self.entry_y=self.menu_y_top+ row_index*(self.y_separation+self.entry_height)

            
    def display_entry_strip(self):
        if self.display_strip=='yes':
            if self.direction=='vertical':
                    #display the strip
                    strip_points=[self.entry_x - self.strip_padding -1 ,
                                  self.entry_y - self.strip_padding - 1,
                                  self.entry_x+ self.entry_width + self.strip_padding - 1,
                                  self.entry_y+self.entry_height+ self.strip_padding - 1]
                    self.canvas.create_rectangle(strip_points,
                                                       outline='',
                                                        fill='gray',
                                                       stipple='gray12',                                 
                                                       tag='pp-content')

                    top_l_points=[self.entry_x - self.strip_padding,
                                  self.entry_y - self.strip_padding,
                                  self.entry_x + self.entry_width + self.strip_padding ,
                                  self.entry_y - self.strip_padding]
                    
                    self.canvas.create_line(top_l_points,
                                            fill='light gray',
                                            tag='pp-content')
                    
                    bottom_l_points=[self.entry_x - self.strip_padding,
                                     self.entry_y + self.entry_height + self.strip_padding,
                                     self.entry_x+ self.entry_width + self.strip_padding ,
                                     self.entry_y+ self.entry_height + self.strip_padding]
                    
                    self.canvas.create_line(bottom_l_points,
                                            fill='dark gray',
                                            tag='pp-content')

                    left_l_points=[self.entry_x - self.strip_padding,
                                   self.entry_y - self.strip_padding,
                                   self.entry_x - self.strip_padding,
                                   self.entry_y + self.entry_height + self.strip_padding]
                    
                    self.canvas.create_line(left_l_points,
                                            fill='gray',
                                            tag='pp-content')

            else:
                    #display the strip vertically
                    strip_points=[self.entry_x - self.strip_padding +1 ,
                                  self.entry_y - self.strip_padding +1,
                                  self.entry_x+self.entry_width + self.strip_padding -1,
                                  self.entry_y + self.entry_height+ self.strip_padding -1]
                    
                    self.canvas.create_rectangle(strip_points,
                                                       outline='',
                                                        fill='gray',
                                                       stipple='gray12',                                 
                                                       tag='pp-content')

                    top_l_points=[self.entry_x - self.strip_padding,
                                  self.entry_y - self.strip_padding,
                                  self.entry_x + self.entry_width + self.strip_padding,
                                  self.entry_y - self.strip_padding]
                    
                    self.canvas.create_line(top_l_points,
                                            fill='light gray',
                                            tag='pp-content')
                    
                    left_l_points=[self.entry_x - self.strip_padding,
                                   self.entry_y - self.strip_padding,
                                   self.entry_x - self.strip_padding,
                                   self.entry_y + self.entry_height+ self.strip_padding]
                    
                    self.canvas.create_line(left_l_points,
                                            fill='gray',
                                            tag='pp-content')

                    right_l_points=[self.entry_x +self.entry_width + self.strip_padding,
                                     self.entry_y - self.strip_padding,
                                     self.entry_x +self.entry_width + self.strip_padding,
                                     self.entry_y + self.entry_height+ self.strip_padding]
                    
                    self.canvas.create_line(right_l_points,
                                            fill='dark gray',
                                            tag='pp-content')


    # display the rectangle that goes arond the icon when the entry is selected
    def display_icon_rectangle(self):
            if self.show_params['menu-icon-mode'] in ('thumbnail','bullet'):

                #calculate icon parameters
                if self.icon_width<self.text_width and self.show_params['menu-text-mode']=='below':
                        self.icon_x_left=self.entry_x+abs(self.icon_width-self.text_width)/2
                else:
                        self.icon_x_left=self.entry_x
                self.icon_x_right=self.icon_x_left+self.icon_width

                if self.icon_height<self.text_height and self.show_params['menu-text-mode']=='right':
                        self.icon_y_top=self.entry_y+abs(self.icon_height-self.text_height)/2
                else:
                        self.icon_y_top=self.entry_y
                self.icon_y_bottom=self.icon_y_top+self.icon_height

                
                req_horiz_sep=self.menu_horizontal_padding
                req_vert_sep=self.menu_vertical_padding

                
                points=[self.icon_x_left,self.icon_y_top,self.icon_x_right,self.icon_y_top,self.icon_x_right,self.icon_y_bottom,self.icon_x_left,self.icon_y_bottom]

                # display guidelines make it white when not selctedfor debugging
                if self.display_guidelines=='always':
                    outline='white'
                else:
                    outline=''

                # and display the icon rectangle
                icon_id=self.canvas.create_polygon(points,
                                                   outline=outline,
                                                   fill='',
                                                   tag='pp-content')


            else:
                # not using icon so set starting point for text to zero icon size
                self.icon_x_right=self.entry_x
                self.icon_y_bottom=self.entry_y
                req_horiz_sep=0
                req_vert_sep=0
                icon_id=None
            return icon_id
        

    #display the image in a menu entry
    def  display_icon_image(self):
            image_id=None
            if self.show_params['menu-icon-mode'] == 'thumbnail':
                # try for the thumbnail
                if self.medialist.selected_track()['thumbnail']<>'' and os.path.exists(self.complete_path(self.medialist.selected_track()['thumbnail'])):
                    self.pil_image=PIL.Image.open(self.complete_path(self.medialist.selected_track()['thumbnail']))
                else:
                    #cannot find thumbnail get the image if its an image track
                    if self.medialist.selected_track()['type'] =='image':
                        self.track=self.complete_path(self.medialist.selected_track()['location'])
                    else:
                        self.track=''
                    if self.medialist.selected_track()['type']=='image' and os.path.exists(self.track)==True: 
                        self.pil_image=PIL.Image.open(self.track)
                    else:
                        #use a standard thumbnail
                        type=self.medialist.selected_track()['type']
                        standard=self.pp_dir+os.sep+'pp_home'+os.sep+'pp_resources'+os.sep+type+'.png'
                        if os.path.exists(standard)==True:
                            self.pil_image=PIL.Image.open(standard)
                            self.mon.log(self,'WARNING: default thumbnail used for '+self.medialist.selected_track()['title'])
                        else:
                            self.pil_image=None

                # display the image                
                if self.pil_image<>None:
                    self.pil_image=self.pil_image.resize((self.icon_width-2,self.icon_height-2))                 
                    image_id=PIL.ImageTk.PhotoImage(self.pil_image)
                    self.canvas.create_image(self.icon_x_left+1, self.icon_y_top+1,
                                                image=image_id, anchor=NW,
                                                 tag='pp-content')
                else:
                        image_id=None
                        
            elif self.show_params['menu-icon-mode'] =='bullet':
                    bullet=self.complete_path(self.show_params['menu-bullet'])                  
                    if os.path.exists(bullet)==False:
                        self.pil_image=None                          
                    else:
                        self.pil_image=PIL.Image.open(bullet)
                    if self.pil_image<>None:
                        self.pil_image=self.pil_image.resize((self.icon_width-2,self.icon_height-2))                 
                        image_id=PIL.ImageTk.PhotoImage(self.pil_image)
                        self.canvas.create_image(self.icon_x_left+1, self.icon_y_top+1,
                                                      image=image_id, anchor=NW,
                                                      tag='pp-content')                                      
            else:
                image_id=None
            return image_id

            
    #display the text of a menu entry
    def display_icon_text(self):
            text_mode=self.show_params['menu-text-mode']
            if self.show_params['menu-icon-mode'] in ('thumbnail','bullet'):
                if text_mode=='right':
                    if self.icon_height>self.text_height:
                        text_y_top=self.entry_y+abs(self.icon_height-self.text_height)/2
                    else:
                        text_y_top=self.entry_y
                    text_y_bottom=text_y_top+self.text_height
                    
                    text_x_left=self.icon_x_right+self.menu_horizontal_padding
                    text_x_right=text_x_left+self.text_width
                    
                    text_x=text_x_left
                    text_y=text_y_top+(self.text_height/2)

                elif text_mode=='below':
                    text_y_top=self.icon_y_bottom+self.menu_vertical_padding
                    text_y_bottom=text_y_top+self.text_height
                    
                    if self.icon_width>self.text_width:
                        text_x_left=self.entry_x+abs(self.icon_width-self.text_width)/2
                    else:
                        text_x_left=self.entry_x
                    text_x_right=text_x_left+self.text_width
                    
                    text_x=text_x_left+(self.text_width/2)
                    text_y=text_y_top

                else:
                    # icon with text_mode=overlay or none
                    text_x_left=self.icon_x_left
                    text_x_right= self.icon_x_right
                    text_y_top=self.icon_y_top
                    text_y_bottom=self.icon_y_bottom
                    text_x=(text_x_left+text_x_right)/2
                    text_y=(text_y_top+text_y_bottom)/2                    

            else:
                    #no icon text only
                    text_y_top=self.entry_y
                    text_y_bottom=text_y_top+self.text_height
                    text_x_left=self.entry_x
                    text_x_right=text_x_left+self.text_width
                    text_x=self.entry_x
                    text_y=self.entry_y+self.text_height/2


            #display the guidelines for debugging
            if self.display_guidelines=='always':
                points=[text_x_left,text_y_top,text_x_right,text_y_top,text_x_right,text_y_bottom,text_x_left,text_y_bottom]
                self.canvas.create_polygon(points,fill= '' ,
                                              outline='white',
                                              tag='pp-content')

            # display the text
            if text_mode=='below' and self.show_params['menu-icon-mode']  in ('thumbnail','bullet'):
                anchor=N
                justify=CENTER
            elif text_mode=='overlay' and self.show_params['menu-icon-mode']  in ('thumbnail','bullet'):
                anchor=CENTER
                justify=CENTER
            else:
                anchor=W
                justify=LEFT
            text_id=self.canvas.create_text(text_x,text_y,
                                       text=self.medialist.selected_track()['title'],
                                       anchor=anchor,
                                       fill=self.show_params['entry-colour'],
                                       font=self.show_params['entry-font'],
                                       width=self.text_width,
                                       justify=justify,
                                       tag='pp-content')
            return text_id
        

    def highlight_menu_entry(self,index,state):
        if self.show_params['menu-icon-mode']<>'none':
            if state==True:
                self.canvas.itemconfig(self.menu_entry_id[index][self.icon_id_index],
                                       outline=self.show_params['entry-select-colour'],
                                       width=4,
                                       )
            else:
                self.canvas.itemconfig(self.menu_entry_id[index][self.icon_id_index],
                                        outline='',
                                       width=1
                                       )
        else:
            if state==True:
                self.canvas.itemconfig(self.menu_entry_id[index][self.text_id_index],
                                       fill=self.show_params['entry-select-colour'])
            else:
                self.canvas.itemconfig(self.menu_entry_id[index][self.text_id_index],
                                    fill=self.show_params['entry-colour'])
                

    
    def display_eggtimer(self,text):
        # print "display eggtimer"
        self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold",
                                                   tag='pp-eggtimer')
        self.canvas.update_idletasks( )


    def delete_eggtimer(self):
        # print"delete eggtimer"
        self.canvas.delete('pp-eggtimer')
        self.canvas.update_idletasks( )


# *********************
# utilities
# *********************

    def complete_path(self,track_file):
        #  complete path of the filename of the selected entry
        if track_file<>'' and track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        return track_file     

    def parse_menu_window(self,line):
            if line<>'':
                fields = line.split()
                if len(fields) not in  (1, 2,4):
                    return 'error','wrong number of fields',0,0,0,0
                if len(fields)==1:
                    if fields[0]=='fullscreen':
                        return 'normal','',0,0,self.screen_width - 1, self.screen_height - 1
                    else:
                        return 'error','single field is not fullscreen',0,0,0,0
                if len(fields)==2:                    
                    if fields[0].isdigit() and fields[1].isdigit():
                        return 'normal','',int(fields[0]),int(fields[1]),self.screen_width, self.screen_height
                    else:
                        return 'error','field is not a digit',0,0,0,0
                if len(fields)==4:                    
                    if fields[0].isdigit() and fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit():
                        return 'normal','',int(fields[0]),int(fields[1]),int(fields[2]),int(fields[3])
                else:
                     return 'error','field is not a digit',0,0,0,0
            else:
                     return 'error','line is blank',0,0,0,0


    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            # timers may be running so need terminate
            self.terminate("error")
        else:
            return value
Example #47
0
class MediaShow:


# *******************
# External interface
# ********************

    def __init__(self,
                            show,
                            canvas,
                            showlist,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the dictionary fo the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show =show
        self.showlist=showlist
        self.canvas=canvas
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()

        # Init variables
        self.player=None
        self.shower=None
        self._poll_for_interval_timer=None
        self._poll_for_continue_timer=None
        self._waiting_for_interval=False
        self._interval_timer=None
        self.error=False
        
        self._interval_timer_signal=False
        self._end_mediashow_signal=False
        self._next_track_signal=False
        self._previous_track_signal=False
        self._play_child_signal = False
        self._req_next='nil'

        self._state='closed'


    def play(self,end_callback,ready_callback=None, top=False,command='nil'):

        """ displays the mediashow
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """

        #instantiate the arguments
        self._end_callback=end_callback
        self._ready_callback=ready_callback
        self.top=top
        self.command=command
        self.mon.log(self,"Starting show: " + self.show['show-ref'])

        # check  data files are available.
        self.media_file = self.pp_profile + "/" + self.show['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self,"Medialist file not found: "+ self.media_file)
            self._end('error',"Medialist file not found")


        #create a medialist for the mediashow and read it.
        self.medialist=MediaList()
        if self.medialist.open_list(self.media_file,self.showlist.sissue())==False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self._end('error',"Version of medialist different to Pi Presents")

        self._wait_for_trigger()


   # respond to key presses.
    def key_pressed(self,key_name):
        self.mon.log(self,"received key: " + key_name)
        
        if key_name=='':
            pass
        
        elif key_name=='escape':
            # if next lower show is running pass down to stop the show and lower level
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            # if not at top stop the show
            else:
                if self.top == False:
                    self._end_mediashow_signal=True
                    # and if a track is running stop that first
                    if self.player<>None:
                        self.player.key_pressed(key_name)
                else:
                    # at top level in a manual presentation stop the track
                    if self.show['progress']=='manual':
                        if self.player<>None:
                            self.player.key_pressed(key_name)
    
        elif key_name in ('up','down'):
        # if child or sub-show is running and is a show pass to show, track does not use up/down
        # otherwise use keys for next or previous
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            else:
                if key_name=='up':
                    self._previous()
                else:
                    self._next()
                
        elif key_name=='return':
            # if child show or sub-show is running and is show - pass down- player does not use return
            # ELSE use Return to start child or to start the show if waiting
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            else:
                if self._state=='playing':
                    if self.show['has-child']=='yes':
                        self._play_child_signal=True
                        # and stop the current track if its running
                        if self.player<>None:
                            self.player.key_pressed("escape")
                else:
                    self._start_show()

        elif key_name=='pir':
                self._start_show()
                
        elif key_name in ('p',' '):
            # pass down if show or track running.
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            elif self.player<>None:
                self.player.key_pressed(key_name)
 

    def button_pressed(self,button,edge):
        if button=='play': self.key_pressed("return")
        elif  button =='up': self.key_pressed("up")
        elif button=='down': self.key_pressed("down")
        elif button=='stop': self.key_pressed("escape")
        elif button=='pause': self.key_pressed('p')
        elif button=='PIR': self.key_pressed('pir')


    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.mon.log(self,"sent terminate to shower")
            self.shower.terminate(reason)
        elif self.player<>None:
            self.mon.log(self,"sent terminate to player")
            self.player.terminate(reason)
        else:
            self._end(reason,'terminated without terminating shower or player')

 
    def _tidy_up(self):
        if self._poll_for_continue_timer<>None:
                self.canvas.after_cancel(self._poll_for_continue_timer)
                self._poll_for_continue_timer=None
        if self._poll_for_interval_timer<>None:
                self.canvas.after_cancel(self._poll_for_interval_timer)
                self._poll_for_interval_timer=None
        if self._interval_timer<>None:
            self.canvas.after_cancel(self._interval_timer)
            self._interval_timer=None

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error")
        else:
            return value

# ***************************
# Respond to key/button presses
# ***************************

    def _stop(self,message):
        self._end_mediashow_signal=True
        if self._interval_timer<>None:
            self.canvas.after_cancel(self._interval_timer)

   
    def _next(self):
        # stop track if running and set signal
        self._next_track_signal=True
        if self.shower<>None:
            self.shower.key_pressed("escape")
        else:
            if self.player<>None:
                self.player.key_pressed("escape")

    def _previous(self):
        self._previous_track_signal=True
        if self.shower<>None:
            self.shower.key_pressed("escape")
        else:
            if self.player<>None:
                self.player.key_pressed("escape")
     
        
# ***************************
# end of show functions
# ***************************

    def _end(self,reason,message):
        self._end_mediashow_signal=False
        self.mon.log(self,"Ending Mediashow: "+ self.show['show-ref'])
        self._tidy_up()
        self._end_callback(reason,message)
        self=None
        return
        



# ***************************
# Show sequencer
# ***************************
 
    def _wait_for_trigger(self):
        self._state='waiting'
        if self.ready_callback<>None:
            self.ready_callback()

        self.mon.log(self,"Waiting for trigger: "+ self.show['trigger'])
        
        if self.show['trigger']=="button":
            # blank screen waiting for trigger if auto, otherwise display something
            if self.show['progress']=="manual":
                text= self.resource('mediashow','m01')
            else:
                text=""
            self.display_message(self.canvas,'text',text,0,self._start_show)


        elif self.show['trigger']=="PIR":
            # blank screen waiting for trigger
            text = self.resource('mediashow','m02')
            self.display_message(self.canvas,'text',text,0,self._start_show)      
            
        elif self.show['trigger']=="start":
            self._start_show()
            
        else:
            self.mon.err(self,"Unknown trigger: "+ self.show['trigger'])
            self._end('error',"Unknown trigger type")
  
        

    def _start_show(self):
        self._state='playing'
        self._direction='forward'
        # start interval timer
        if self.show['repeat']=="interval" and self.show['repeat-interval']<>0:
            self._interval_timer_signal=False
            self._interval_timer=self.canvas.after(int(self.show['repeat-interval'])*1000,self._end_interval_timer)
        # and play the first track unless commanded otherwise
        if self.command=='backward':
            self.medialist.finish()
        else:
            self.medialist.start()
        self._play_selected_track(self.medialist.selected_track())
 
 
    def _what_next(self):
        self._direction='forward'
        
        # user wants to end, wait for any shows or tracks to have ended then end show
        if self._end_mediashow_signal==True:
            if self.player==None and self.shower==None:
                self._end_mediashow_signal=False
                self._end('normal',"show ended by user")
            else:
                pass
            
        #returning from a subshow needing to move onward 
        elif self._req_next=='do-next':
            self._req_next='nil'
            self.medialist.next()
            self._play_selected_track(self.medialist.selected_track())
            
        #returning from a subshow needing to move backward 
        elif self._req_next=='do-previous':
            self._req_next='nil'
            self._direction='backward'
            self.medialist.previous()
            self._play_selected_track(self.medialist.selected_track())         
               
        # user wants to play child
        elif self._play_child_signal == True:
            self._play_child_signal=False
            index = self.medialist.index_of_track('pp-child-show')
            if index >=0:
                #don't select the track as need to preserve mediashow sequence.
                child_track=self.medialist.track(index)
                self._display_eggtimer(self.resource('mediashow','m07'))
                self._play_selected_track(child_track)
            else:
                self.mon.err(self,"Child show not found in medialist: "+ self.show['pp-child-show'])
                self._end('error',"child show not found in medialist")
        
        # skip to next track on user input
        elif self._next_track_signal==True:
            self._next_track_signal=False
            if self.medialist.at_end()==True:
                if  self.show['sequence']=="ordered" and self.show['repeat']=='oneshot' and self.top==False:
                    self._end('do-next',"Return from Sub Show")
                else:
                    self.medialist.next()
                    self._play_selected_track(self.medialist.selected_track())               
            else:
                self.medialist.next()
                self._play_selected_track(self.medialist.selected_track())
                
        # skip to previous track on user input
        elif self._previous_track_signal==True:
            self._previous_track_signal=False
            self._direction='backward'
            if self.medialist.at_start()==True:
                if  self.show['sequence']=="ordered" and self.show['repeat']=='oneshot' and self.top==False:
                    self._end('do-previous',"Return from Sub Show")
                else:
                    self.medialist.previous()
                    self._play_selected_track(self.medialist.selected_track())               
            else:
                self.medialist.previous()              
                self._play_selected_track(self.medialist.selected_track())
        

        # track is finished and we are on auto        
        elif self.show['progress']=="auto":
            if self.medialist.at_end()==True:
                if self.show['sequence']=="ordered" and self.show['repeat']=='oneshot' and self.top==False:
                    self._end('do-next',"Return from Sub Show")
                    
                #### elif    
                elif self.show['sequence']=="ordered" and self.show['repeat']=='oneshot' and self.top==True:
                    self._wait_for_trigger()

                elif self._waiting_for_interval==True:
                    if self._interval_timer_signal==True:
                        self._interval_timer_signal=False
                        self._waiting_for_interval=False
                        self._start_show()
                    else:
                        self._poll_for_interval_timer=self.canvas.after(1000,self._what_next)
 
                elif self.show['sequence']=="ordered" and self.show['repeat']=='interval' and int(self.show['repeat-interval'])>0:
                    self._waiting_for_interval=True
                    self._poll_for_interval_timer=self.canvas.after(1000,self._what_next) 
                    
                elif self.show['sequence']=="ordered" and self.show['repeat']=='interval' and int(self.show['repeat-interval'])==0:
                    self.medialist.next()
                    self._play_selected_track(self.medialist.selected_track())
                           
                else:
                    self.mon.err(self,"Unhandled playing event: ")
                    self._end('error',"Unhandled playing event")
                    
            else:
                self.medialist.next()
                self._play_selected_track(self.medialist.selected_track())
                    
        # track has finished and we are on manual progress               
        elif self.show['progress']=="manual":
                    self._delete_eggtimer()
                    self._display_eggtimer(self.resource('mediashow','m03'))
                    self._poll_for_continue_timer=self.canvas.after(500,self._what_next)
                    
        else:
            #unhandled state
            self.mon.err(self,"Unhandled playing event: ")
            self._end('error',"Unhandled playing event")           



    def _end_interval_timer(self):
        self._interval_timer_signal=True
 

        

# ***************************
# Dispatching to Players/Shows 
# ***************************

    # used to display internal messages in situations where a medialist entry could be used.
    def display_message(self,canvas,source,content,duration,_display_message_callback):
            self._display_message_callback=_display_message_callback
            tp={'duration':duration,'message-colour':'white','message-font':'Helvetica 20 bold'}
            self.player=MessagePlayer(canvas,tp,tp)
            self.player.play(content,self._display_message_end,None)

    def   _display_message_end(self,reason,message):
        self.player=None
        if reason in ('error','killed'):
            self._end(reason,message)
        else:
            self._display_message_callback()


    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to play is: "+ track_file)
        return track_file     
         
        


    def _play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
        self.canvas.delete(ALL)
        if self.show['progress']=="manual":
            self._display_eggtimer(self.resource('mediashow','m04'))

        # is menu required
        if self.show['has-child']=="yes":
            enable_child=True
        else:
            enable_child=False

        #dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
        
        if track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.canvas,self.show,selected_track)
            self.player.play(track_file,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=enable_child)
                                        
        elif track_type=="image":
            track_file=self.complete_path(selected_track)
            # images played from menus don't have children
            self.player=ImagePlayer(self.canvas,self.show,selected_track)
            self.player.play(track_file,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=enable_child)
                                    
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.canvas,self.show,selected_track)
            self.player.play(text,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=enable_child
                                    )
         
 
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self._end('error',"Unknown show")
                
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.end_shower,top=False,command=self._direction)

            elif selected_show['type']=="liveshow":    
                self.shower= LiveShow(selected_show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.end_shower,top=False,command='nil')
            
            elif selected_show['type']=="menu":
                self.shower= MenuShow(selected_show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.end_shower,top=False,command='nil')
                
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self._end('error'"Unknown show type")  
            
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self._end('error',"Unknown track type")            


    def ready_callback(self):
        self._delete_eggtimer()
        
        
    def end_player(self,reason,message):
        self._req_next='nil'
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        if reason in("killed","error"):
            self._end(reason,message)
        elif self.show['progress']=="manual":
            self._display_eggtimer(self.resource('mediashow','m05'))
            self._req_next=reason
            self._what_next()
        else:
            self._req_next=reason
            self._what_next()

    def end_shower(self,reason,message):
        self._req_next='nil'
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        if reason in("killed","error"):
            self._end(reason,message)
        elif self.show['progress']=="manual":
            self._display_eggtimer(self.resource('mediashow','m06'))
            self._req_next=reason
            self._what_next() 
        else:
            self._req_next=reason
            self._what_next() 
        
        
    def _display_eggtimer(self,text):
        self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold")
        self.canvas.update_idletasks( )


    def _delete_eggtimer(self):
            self.canvas.delete(ALL)
Example #48
0
class OSCMonitor(object):
    def __init__(self):

        # get command options
        self.command_options = remote_options()

        # get directory holding the code
        self.pp_dir = sys.path[0]

        if not os.path.exists(self.pp_dir + os.sep + "pipresents.py"):
            tkinter.messagebox.showwarning("Pi Presents",
                                           "Bad Application Directory")
            exit()

        # Initialise logging
        Monitor.log_path = self.pp_dir
        self.mon = Monitor()
        self.mon.init()

        Monitor.classes = ['OSCMonitor', 'OSCConfig', 'OSCEditor']

        Monitor.log_level = int(self.command_options['debug'])

        self.mon.log(self, "Pi Presents Monitor is starting")
        self.mon.log(self, " OS and separator " + os.name + '  ' + os.sep)
        self.mon.log(self,
                     "sys.path[0] -  location of code: code " + sys.path[0])

        self.root = Tk()

        # initialise OSC config class
        self.osc_config = OSCConfig()

        # read the options and allow their editing
        self.osc_config_file = self.pp_dir + os.sep + 'pp_config' + os.sep + 'pp_oscmonitor.cfg'
        self.read_create_osc()

        if self.osc_config.slave_enabled != 'yes':
            self.mon.err(self, 'OSC Slave is not enabled in pp_oscmonitor.cfg')
            exit()

        #build gui
        self.setup_gui()

        # initialise
        self.init()

        #and start the system
        self.root.after(1000, self.run_app)
        self.root.mainloop()

    def init(self):
        self.desc.set('Listening for Commands from Master on: ' +
                      self.osc_config.this_unit_ip + ':' +
                      self.osc_config.listen_port)
        self.client = None
        self.server = None
        self.st = None

    def add_status(self, text):
        self.status_display.insert(END, text + '\n')
        self.status_display.see(END)

    def run_app(self):

        if self.osc_config.slave_enabled != 'yes':
            self.mon.err(self, 'Slave not enabled in oscmonitor.cfg')
            return

        if self.osc_config.this_unit_ip == '':
            self.mon.err(self,
                         'IP of own unit must be provided in oscmonitor.cfg')
            return

        if self.osc_config.listen_port == '':
            self.mon.err(self,
                         'Listen port must be provided in oscmonitor.cfg')
            return

        self.client = None
        self.server = None
        self.st = None

        # initialise OSC variables

        self.prefix = '/pipresents'
        self.this_unit = '/' + self.osc_config.this_unit_name
        self.add_status('this unit OSC address is: ' + self.this_unit)
        self.add_status('Listening for Commands from Master on: ' +
                        self.osc_config.this_unit_ip + ':' +
                        self.osc_config.listen_port)

        #connect client for replies then start server to listen for commands
        self.client = OSC.OSCClient()
        self.init_server(self.osc_config.this_unit_ip,
                         self.osc_config.listen_port, self.client)
        self.add_initial_handlers()
        self.start_server()

    # ***************************************
    # OSC CLIENT TO SEND REPLIES
    # ***************************************

    def disconnect_client(self):
        if self.client != None:
            self.client.close()
        return

    # ***************************************
    # OSC SERVER TO LISTEN TO COMMANDS
    # ***************************************

    def init_server(self, ip, port_text, client):
        self.mon.log(self, 'Init Server: ' + ip + ':' + port_text)
        self.server = myOSCServer((ip, int(port_text)), client)

    def start_server(self):
        self.st = threading.Thread(target=self.server.serve_forever)
        self.st.start()

    def close_server(self):
        if self.server != None:
            self.server.close()
        self.mon.log(self, 'Waiting for Server-thread to finish')
        if self.st != None:
            self.st.join()  ##!!!
        self.mon.log(self, 'server thread closed')

    def add_initial_handlers(self):
        pass
        self.server.addMsgHandler('default', self.no_match_handler)
        self.server.addMsgHandler(
            self.prefix + self.this_unit + "/system/server-info",
            self.server_info_handler)
        self.server.addMsgHandler(
            self.prefix + self.this_unit + "/system/loopback",
            self.loopback_handler)

    def no_match_handler(self, addr, tags, stuff, source):
        text = "Message from %s" % OSC.getUrlStr(source) + '\n'
        text += "     %s" % addr + self.pretty_list(stuff)
        self.add_status(text + '\n')

    def server_info_handler(self, addr, tags, stuff, source):
        # send a reply to the client.
        msg = OSC.OSCMessage(self.prefix + '/system/server-info-reply')
        msg.append(self.osc_config.this_unit_name)
        msg.append(self.server.getOSCAddressSpace())

        text = "Message from %s" % OSC.getUrlStr(source) + '\n'
        text += "     %s" % addr + self.pretty_list(stuff)
        self.add_status(text)
        self.add_status('Sent reply to Server Info request to %s:' %
                        OSC.getUrlStr(source) + '\n')
        return msg

    def loopback_handler(self, addr, tags, stuff, source):

        # send a reply to the client.
        msg = OSC.OSCMessage(self.prefix + '/system/loopback-reply')

        text = "Message from %s" % OSC.getUrlStr(source) + '\n'
        text += "     %s" % addr + self.pretty_list(stuff)
        self.add_status(text + '\n' + 'Sent reply to Loopback request to %s:' %
                        OSC.getUrlStr(source) + '\n')
        return msg

    def pretty_list(self, fields):
        text = ' '
        for field in fields:
            text += str(field) + ' '
        return text

    # ***************************************
    # INIT EXIT MISC
    # ***************************************

    def e_edit_osc(self):
        self.disconnect_client()
        self.close_server()
        self.edit_osc()
        self.read_create_osc()
        self.init()
        self.add_status('\n\n\nRESTART')
        self.run_app()

    def app_exit(self):
        self.disconnect_client()
        self.close_server()
        if self.root is not None:
            self.root.destroy()
        self.mon.finish()
        sys.exit()

    def show_help(self):
        tkinter.messagebox.showinfo("Help", "Read 'manual.pdf'")

    def about(self):
        tkinter.messagebox.showinfo(
            "About", "Simple Remote Monitor for Pi Presents\n" +
            "Author: Ken Thompson" +
            "\nWebsite: http://pipresents.wordpress.com/")

    def setup_gui(self):
        # set up the gui

        # root is the Tkinter root widget
        self.root.title("Remote Monitor for Pi Presents")

        # self.root.configure(background='grey')

        self.root.resizable(False, False)

        # define response to main window closing
        self.root.protocol("WM_DELETE_WINDOW", self.app_exit)

        # bind some display fields
        self.desc = StringVar()
        self.filename = StringVar()
        self.display_show = StringVar()
        self.results = StringVar()
        self.status = StringVar()

        # define menu
        menubar = Menu(self.root)

        osc_configmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Options', menu=osc_configmenu)
        osc_configmenu.add_command(label='Edit', command=self.e_edit_osc)

        helpmenu = Menu(menubar, tearoff=0, bg="grey", fg="black")
        menubar.add_cascade(label='Help', menu=helpmenu)
        helpmenu.add_command(label='Help', command=self.show_help)
        helpmenu.add_command(label='About', command=self.about)

        self.root.config(menu=menubar)

        # info frame
        info_frame = Frame(self.root, padx=5, pady=5)
        info_frame.pack(side=TOP, fill=BOTH, expand=1)
        info_name = Label(info_frame,
                          text="Slave Unit's Name: " +
                          self.osc_config.this_unit_name,
                          font="arial 12 bold")
        info_name.pack(side=TOP)
        info_this_address = Label(info_frame,
                                  textvariable=self.desc,
                                  font="arial 12 bold")
        info_this_address.pack(side=TOP)

        # status_frame
        status_frame = Frame(self.root, padx=5, pady=5)
        status_frame.pack(side=TOP, fill=BOTH, expand=1)
        status_label = Label(status_frame,
                             text="Status:",
                             font="arial 12 bold")
        status_label.pack(side=LEFT)
        scrollbar = Scrollbar(status_frame, orient=VERTICAL)
        self.status_display = Text(status_frame,
                                   height=20,
                                   yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.status_display.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.status_display.pack(side=LEFT, fill=BOTH, expand=1)


# ***************************************
#  OSC CONFIGURATION
# ***************************************

    def read_create_osc(self):
        if self.osc_config.read(self.osc_config_file) is False:
            self.osc_config.create(self.osc_config_file, 'slave')
            eosc = OSCEditor(self.root, self.osc_config_file, 'slave',
                             'Create OSC Monitor Configuration')
            self.osc_config.read(self.osc_config_file)

    def edit_osc(self):
        if self.osc_config.read(self.osc_config_file) is False:
            self.osc_config.create(self.osc_config_file)
        eosc = OSCEditor(self.root, self.osc_config_file, 'slave',
                         'Edit OSC Monitor Configuration')
Example #49
0
class KbdDriver:

    config=None

    def __init__(self):
        self.mon=Monitor()
        self.mon.on()

    # sets up tkinter keyboard events such that any key press
    # does a callback to 'callback' with the event object and a symbolic name.
    def bind_keys(self,widget,callback):
        for option in KbdDriver.config.items('keys'):
            condition=option[0]
            symbolic_name=option[1]
            # print condition,symbolic_name
            widget.bind(condition, lambda event, name=symbolic_name: self.specific_key(callback,name))

        # bind all the normal keys that return a printing character such that x produces pp-key-x
        widget.bind("<Key>", lambda event : self.normal_key(callback,event))

##        # bind special keys to the specified symbolic name
##        widget.bind("<Break>", lambda event, name='pp-exit': self.specific_key(callback,name))
##        widget.bind("<Escape>", lambda event, name='pp-stop': self.specific_key(callback,name))
##        widget.bind("<Up>", lambda event, name='pp-up': self.specific_key(callback,name))
##        widget.bind("<Down>", lambda event, name='pp-down': self.specific_key(callback,name))
##        widget.bind("<Return>", lambda event, name='pp-play': self.specific_key(callback,name))
##


    def specific_key(self,callback,name):
        callback(name,'front','key')

    # alphanumeric keys- convert to symbolic by adding pp-key-
    def normal_key(self,callback,event):
        key=event.char
        if key<>'':
            callback('pp-key-'+key,'front','key')



     #read the key bindings from keys.cfg
    def read(self,pp_dir,pp_home,pp_profile):
        if KbdDriver.config==None:
            # try inside profile
            tryfile=pp_profile+os.sep+"keys.cfg"
            # self.mon.log(self,"Trying keys.cfg in profile at: "+ tryfile)
            if os.path.exists(tryfile):
                 filename=tryfile
            else:
                # try inside pp_home
                # self.mon.log(self,"keys.cfg not found at "+ tryfile+ " trying pp_home")
                tryfile=pp_home+os.sep+"keys.cfg"
                if os.path.exists(tryfile):
                    filename=tryfile
                else:
                    # try inside pipresents
                    # self.mon.log(self,"keys.cfg not found at "+ tryfile + " trying inside pipresents")
                    tryfile=pp_dir+os.sep+'pp_home'+os.sep+"keys.cfg"
                    if os.path.exists(tryfile):
                        filename=tryfile
                    else:
                        self.mon.log(self,"keys.cfg not found at "+ tryfile)
                        self.mon.err(self,"keys.cfg not found")
                        return False   
            KbdDriver.config = ConfigParser.ConfigParser()
            KbdDriver.config.optionxform=str
            KbdDriver.config.read(filename)
            self.mon.log(self,"keys.cfg read from "+ filename)
            if KbdDriver.config.has_section('keys')==False:
                self.mon.err(self,"no [keys] section in keys.cfg")
                return False
            return True

    def has_section(self,section):
        if KbdDriver.config.has_section('keys')==False:
            return False
Example #50
0
class MediaList:
    """
    manages a media list of tracks and the track selected from the medialist
    """
    def __init__(self):
        self.clear()
        self.mon = Monitor()
        self.mon.on()

# Functions for the editor dealing with complete list

    def clear(self):
        self._tracks = []  #MediaList, stored as a list of dicts
        self._num_tracks = 0
        self._selected_track_index = -1  # index of currently selected track

    def print_list(self):
        print self._tracks

    def first(self):
        self.select(0)

    def length(self):
        return self._num_tracks

    def append(self, track_dict):
        """appends a track dictionary to the end of the medialist store"""
        self._tracks.append(copy.deepcopy(track_dict))
        self._num_tracks += 1

    def update(self, index, values):
        self._tracks[index].update(values)

    def remove(self, index):
        self._tracks.pop(index)
        self._num_tracks -= 1
        # deselect any track, saves worrying about whether index needs changing
        self._selected_track_index = -1

    def move_up(self):
        if self._selected_track_index <> 0:
            self._tracks.insert(self._selected_track_index - 1,
                                self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index - 1)

    def move_down(self):
        if self._selected_track_index <> self._num_tracks - 1:
            self._tracks.insert(self._selected_track_index + 1,
                                self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index + 1)

    def replace(self, index, replacement):
        self._tracks[index] = replacement

# Common functions work for anything

    def track_is_selected(self):
        if self._selected_track_index >= 0:
            return True
        else:
            return False

    def selected_track_index(self):
        return self._selected_track_index

    def track(self, index):
        return self._tracks[index]

    def selected_track(self):
        """returns a dictionary containing all fields in the selected track """
        return self._selected_track

    def select(self, index):
        """does housekeeping necessary when a track is selected"""
        if self._num_tracks > 0 and index >= 0 and index < self._num_tracks:
            self._selected_track_index = index
            self._selected_track = self._tracks[index]
            return True
        else:
            return False

# Dealing with anonymous tracks for use and display

    def at_end(self):
        # true is selected track is last anon
        index = self._num_tracks - 1
        while index >= 0:
            if self._tracks[index]['track-ref'] == "":
                end = index
                if self._selected_track_index == end:
                    return True
                else:
                    return False
            index -= 1
        return False

    def index_of_end(self):
        index = self._num_tracks - 1
        while index >= 0:
            if self._tracks[index]['track-ref'] == "":
                return index
            index -= 1
        return -1

    def at_start(self):
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                start = index
                if self._selected_track_index == start:
                    return True
                else:
                    return False
            index += 1
        return False

    def index_of_start(self):
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                return index
            index += 1
        return False

    def display_length(self):
        count = 0
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                count += 1
            index += 1
        return count

    def start(self):
        # select first anymous track in the list
        index = 0
        while index < self._num_tracks:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            index += 1
        return False

    def finish(self):
        # select first anymous track in the list
        index = self._num_tracks - 1
        while index >= 0:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            index -= 1
        return False

    def next(self):
        if self._selected_track_index == self._num_tracks - 1:
            index = 0
        else:
            index = self._selected_track_index + 1
        end = self._selected_track_index
        while index <> end:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            if index == self._num_tracks - 1:
                index = 0
            else:
                index = index + 1
        return False

    def previous(self):
        if self._selected_track_index == 0:
            index = self._num_tracks - 1
        else:
            index = self._selected_track_index - 1
        end = self._selected_track_index
        while index <> end:
            if self._tracks[index]['track-ref'] == "":
                self.select(index)
                return True
            if index == 0:
                index = self._num_tracks - 1
            else:
                index = index - 1
        return False

# Lookup for labelled tracks

    def index_of_track(self, wanted_track):
        index = 0
        for track in self._tracks:
            if track['track-ref'] == wanted_track:
                return index
            index += 1
        return -1

# open and save

    def open_list(self, filename, showlist_issue):
        """
        opens a saved medialist
        medialists are stored as json arrays.
        """
        ifile = open(filename, 'rb')
        mdict = json.load(ifile)
        ifile.close()
        self._tracks = mdict['tracks']
        if 'issue' in mdict:
            self.issue = mdict['issue']
        else:
            self.issue = "1.0"
        if self.issue == showlist_issue:
            self._num_tracks = len(self._tracks)
            self._selected_track_index = -1
            return True
        else:
            return False

    def issue(self):
        return self.issue

    def save_list(self, filename):
        """ save a medialist """
        if filename == "":
            return False
        dic = {'issue': self.issue, 'tracks': self._tracks}
        filename = str(filename)
        filename = string.replace(filename, '\\', '/')
        tries = 1
        while tries <= 10:
            # print "save  medialist  ",filename
            try:
                ofile = open(filename, "wb")
                json.dump(dic, ofile, sort_keys=True, indent=1)
                ofile.close()
                self.mon.log(self, "Saved medialist " + filename)
                break
            except IOError:
                self.mon.err(
                    self,
                    "failed to save medialist, trying again " + str(tries))
                tries += 1
        return

# for the future

    def open_csv(self, filename):
        """
        opens a saved csv medialist
        """
        if filename != "" and os.path.exists(filename):
            ifile = open(filename, 'rb')
            pl = csv.reader(ifile)
            for pl_row in pl:
                if len(pl_row) != 0:
                    entry = dict([('type', pl_row[2]), ('location', pl_row[0]),
                                  ('title', pl_row[1])])
                    self.append(copy.deepcopy(entry))
            ifile.close()
            return True
        else:
            return False
class MediaList(object):
    """
    manages a media list of tracks and the track selected from the medialist
    """
    

    def __init__(self,sequence):
        self.clear()
        self.mon=Monitor()
        self.sequence=sequence
        
 # Functions for the editor dealing with complete list

    def clear(self):
        self._tracks = []  #MediaList, stored as a list of dicts
        self._num_tracks=0
        self._selected_track_index=-1 # index of currently selected track

    def print_list(self):
        print('\n')
        print(self._tracks)

    def first(self):
        self._selected_track_index=-1
        self.next(self.sequence) #let this do the work of randomaising or  advancing to 0

    def length(self):
        return self._num_tracks

    def append(self, track_dict):
        # print '\ntrack dict',track_dict
        """appends a track dictionary to the end of the medialist store"""
        self._tracks.append(copy.deepcopy(track_dict))
        self._num_tracks+=1

    def update(self,index,values):
        self._tracks[index].update(values)


    def remove(self,index):
        self._tracks.pop(index)
        self._num_tracks-=1
        # deselect any track, saves worrying about whether index needs changing
        self._selected_track_index=-1

    def move_up(self):
        if self._selected_track_index != 0:
            self._tracks.insert(self._selected_track_index-1, self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index-1)

    def move_down(self):
        if self._selected_track_index != self._num_tracks-1:
            self._tracks.insert(self._selected_track_index+1, self._tracks.pop(self._selected_track_index))
            self.select(self._selected_track_index+1)

    def copy(self):
        self._tracks.insert(self._selected_track_index+1, copy.deepcopy(self._tracks[self._selected_track_index]))
        self._num_tracks+=1
        self.select(self._selected_track_index+1)


    def replace(self,index,replacement):
        self._tracks[index]= replacement     
        
        
# Common functions work for anything
           

    def track_is_selected(self):
        if self._selected_track_index>=0:
            return True
        else:
            return False
            
    def selected_track_index(self):
        return self._selected_track_index

    def track(self,index):
        return self._tracks[index]

    def selected_track(self):
        """returns a dictionary containing all fields in the selected track """
        return self._selected_track

    def select(self,index):
        """does housekeeping necessary when a track is selected"""
        if self._num_tracks>0 and index>=0 and index< self._num_tracks:
            self._selected_track_index=index
            self._selected_track = self._tracks[index]
            return True
        else:
            return False

# Dealing with anonymous tracks for use and display

  
     
    def at_end(self):
        # true is selected track is last anon
        index=self._num_tracks-1
        while index>=0:
            if self._tracks[index] ['track-ref'] =="":
                end=index
                if self._selected_track_index==end:
                    return True
                else:
                    return False
            index -=1
        return False
        
        
    def index_of_end(self):
        if self.anon_length()==0:
            return False
        index=self._num_tracks-1
        while index >= 0:
            if self._tracks[index] ['track-ref'] =="":
                return index
            index -=1
        return -1
   
   
    def at_start(self):
        if self.anon_length()==0:
            return False
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                start =  index
                if self._selected_track_index==start:
                    return True
                else:
                    return False
            index +=1
        return False
   
            
    def index_of_start(self):
        if self.anon_length()==0:
            return False
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                return index
            index +=1
        return False


    def anon_length(self):
        # number of anonymous tracks
        count=0
        index=0
        while index<self._num_tracks:
            if self._tracks[index] ['track-ref'] =="":
                count+=1
            index +=1
        return count

    def start(self):
        if self.anon_length()==0:
            return False
        # select first anonymous track in the list
        if self.sequence == 'ordered':
            index=0
            while index<self._num_tracks:
                if self._tracks[index] ['track-ref'] =="":
                    self.select(index)
                    return True
                index +=1
            return False
        else:
            match=random.randint(0,self.anon_length()-1)
            # print 'match',match
            index=0
            while index<self._num_tracks:
                if self._tracks[index] ['track-ref'] =="" and index==match:
                    self.select(index)
                    # print index
                    return index
                index +=1

    def finish(self):
        if self.anon_length()==0:
            return False
        if self.sequence == 'ordered':
            # select last anymous track in the list
            index=self._num_tracks-1
            while index>=0:
                if self._tracks[index] ['track-ref'] =="":
                    self.select(index)
                    return True
                index -=1
            return False
        else:
            match=random.randint(0,self.anon_length()-1)
            # print 'match',match
            index=0
            while index<self._num_tracks:
                if self._tracks[index] ['track-ref'] =="" and index==match:
                    self.select(index)
                    # print index
                    return index
                index +=1
        

    def select_anon_by_index(self,wanted):
        if self.anon_length()==0:
            return False
        index=0
        anon_index=0
        while index != self._num_tracks:
            # print index,self._tracks[index] ['track-ref'],wanted
            if self._tracks[index] ['track-ref'] =="":
                if anon_index==wanted:
                    # print 'match\n'
                    self.select(index)
                    return True
                anon_index+=1
            index= index+1
        return False


    def next(self,sequence):
        if self.anon_length()==0:
            return False
        if sequence=='ordered':
            if self._selected_track_index== self._num_tracks-1:
                index=0
            else:
                index= self._selected_track_index+1
                
            end=self._selected_track_index
        else:
            index=random.randint(0,self.anon_length()-1)
            if index==0:
                end=self._num_tracks-1
            else:
                end=index-1
        # search for next anonymous track
        # print 'index', index, 'end',end
        while index != end:
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True
            if index== self._num_tracks-1:
                index=0
            else:
                index= index+1
        return False

    def previous(self,sequence):
        if self.anon_length()==0:
            return False
        if sequence=='ordered':
            if self._selected_track_index == 0:
                index=self._num_tracks-1
            else:
                index= self._selected_track_index-1
            end = self._selected_track_index
        else:
            index=random.randint(0,self.anon_length()-1)
            if index==self._num_tracks-1:
                end=0
            else:
                end=index+1
        # print 'index', index, 'end',end                
        # search for previous anonymous track            
        while index != end :
            if self._tracks[index] ['track-ref'] =="":
                self.select(index)
                return True                
            if index == 0:
                index=self._num_tracks-1
            else:
                index= index-1
        return False
    
    
# Lookup for labelled tracks
    
    
    def index_of_track(self,wanted_track):
        index = 0
        for track in self._tracks:
            if track['track-ref']==wanted_track:
                return index
            index +=1
        return -1




# open and save

    def add_track_for_file(self, filename, filetype):
        """
        Simulates adding an image or video as if it was coming from a media list.
        :param filename: The image to add
        :return:
        """

        track = {}
        track["type"] = filetype
        track["location"] = filename
        track["track-ref"] = ""
        track["background-image"] = ""
        track["background-colour"] = ""
        track["animate-begin"] = ""
        track["animate-end"] = ""
        track["duration"] = ""
        track["image-window"] = "fit"
        track["image-rotation"] = ""
        track["image-rotate"] = "0"
        track["pause-timeout"] = ""
        track["plugin"] = ""
        track["track-html-background-colour"] = "white"
        track["track-html-height"] = "300"
        track["track-html-width"] = "300"
        track["track-text-x"] = ""
        track["track-text-y"] = ""
        track["track-text-justify"] = ""
        track["track-text-font"] = ""
        track["track-text-colour"] = ""
        track["track-text-location"] = ""
        track["track-text-type"] = "plain"
        track["track-text"] = ""
        track["display-show-background"] = ""
        track["show-control-begin"] = ""
        track["show-control-end"] = ""
        track["thumbnail"] = ""
        track["transition"] = "cut"
        track["pause-text"] = ""
        track["animate-clear"] = ""

        track["omx-audio"] = ""
        track["cmx-audio"] = ""
        track["omx-volume"] = ""
        track["omx-window"] = ""
        track["omx-other-options"] = ""
        track["freeze-at-start"] = ""
        track["freeze-at-end"] = ""
        track["seamless-loop"] = ""
        track["pause-timeout"] = ""

        self._tracks.append(track)
        self._num_tracks = len(self._tracks)
        self.mon.log(self, "Added file " + filename)

    def open_directory(self,directory, match_glob, exclude_regex):
        """
        Opens a directory as if it was a tracklist of all the files in the directory.
        Right now, we're hard-coding the files to look for to .jpg files ... but
        extending it would be fairly straightforward ...
        :param directory: Directory to scan for files and load everything with a matching extension.  Lame
                          error-checking -- be sure no trailing slash
        :param match_glob: A set of file-specs to scan for and match.  The list itself is separated by |.  Then
                           each item in the list consists of a glob-type match, separated by # from the type
                           (image or video) that we're matching.
                           i.e., glob#type|glob#type|glob#type
                           e.g., /**/*.jpg#image|/**/*.mpg#video
        :param exclude_regex: A regular expression indicating files to skip
        :return:
        """

        self.clear()

        self.mon.log(self, "Adding files matching " + match_glob + " from " + directory)
        self.mon.log(self, "Excluding files: " + exclude_regex)

        import glob
        import re
        if len(exclude_regex) > 0:
            matcher = re.compile(exclude_regex)
        else:
            matcher = None

        pats = match_glob.split("|")
        for each_pat in pats:
            thisglob, type = each_pat.split("#")
            self.mon.log(self, "Matching glob " + thisglob)
            for filename in glob.iglob(directory + thisglob):
                if matcher is None or not matcher.match(filename):
                    self.add_track_for_file(filename, type)

        self.mon.log(self, "Loaded " + str(self._num_tracks) + " files.")

        self.last_num_tracks = self._num_tracks

        return True


    def open_list(self,filename,profile_version):
        """
        opens a saved medialist
        medialists are stored as json arrays.
        """
        ifile  = open(filename, 'r')
        mdict = json.load(ifile)
        ifile.close()
        if 'issue' in mdict:
            self.medialist_version_string= mdict['issue']
        else:
            self.medialist_version_string="1.0"

        # If the media file contains a loaddir key, then treat this as a directory to load, instead of a single file.
        #   loaddir:  the directory to (recursively) load
        #   exclude-regex:  optional - a regular expression to use to exclude files from the load (I use to exclude
        #                   directories)
        #   match-glob: a list of glob specs and types separated by |, where the glob spec and type are separated by #
        # e.g.:
        #   "loaddir":"/media/some-pictures"
        #   "exclude-regex":".*\\/(skip-this-dir|skip-this-dir-also)\\/.*"
        #   "match-glob":"//**/*.jpg#image|/**/*.JPG#image|/**/*.gif#image|/**/*.GIF#image|/***/*.mpg#video"
        if 'loaddir' in mdict:
            if 'match-glob' in mdict:
                match_glob = mdict['match-glob']
            else:
                match_glob = "/**/*.jpg"
            if 'exclude-regex' in mdict:
                exclude_regex = mdict['exclude-regex']
            else:
                exclude_regex = ''
            return self.open_directory(mdict['loaddir'], match_glob, exclude_regex)
        else:
            self._tracks = mdict['tracks']

            if self.medialist_version()==profile_version:
                self._num_tracks=len(self._tracks)
                self.last_num_tracks=self._num_tracks
                self._selected_track_index=-1
                return True
            else:
                return False

    def medialist_version(self):
        vitems=self.medialist_version_string.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])



    # dummy for mediliast, in livelist the list is created from the live_track directories
    def use_new_livelist(self):
        pass

    def create_new_livelist(self):
        pass

    def new_length(self):
        return self.length()

    # for medialist the content of the list never changes so return False
    def livelist_changed(self):
            return False

    def save_list(self,filename):
        """ save a medialist """
        if filename=="":
            return False
        dic={'issue':self.medialist_version_string,'tracks':self._tracks}
        filename=str(filename)
        filename = str.replace(filename,'\\','/')
        tries = 1
        while tries<=10:
            # print "save  medialist  ",filename
            try:
                ofile  = open(filename, "w")
                json.dump(dic,ofile,sort_keys=True,indent=1)
                ofile.close()
                self.mon.log(self,"Saved medialist "+ filename)
                break
            except IOError:
                self.mon.err(self,"failed to save medialist, trying again " + str(tries))
                tries+=1
        return
class MenuShow:
    """ Displays a menu with optional hint below it. User can traverse the menu and
              select a track using key or button presses.
        Interface:
         * play - displays the menu and selects the first entry
         * key_pressed, button_pressed - receives user events passes them to a Player if a track is playing,
                otherwise actions them with _next, _previous, _play_selected_track, _end
         Optional display of eggtimer by means of Players ready_callback
         Supports imageplayer, videoplayer,messagplayer,audioplayer,menushow,mediashow
         Destroys itself on exit
    """

# *********************
# external interface
# ********************

    def __init__(self,
                            show_params,
                            canvas,
                            showlist,
                            pp_home,
                            pp_profile):
        """ canvas - the canvas that the menu is to be written on
            show - the name of the configuration dictionary section for the menu
            showlist  - the showlist
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory"""
        
        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_params=show_params
        self.showlist=showlist
        self.canvas=canvas
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()
        
        # init variables
        self.drawn  = None
        self.player=None
        self.shower=None
        self.menu_timeout_running=None
        self.error=False



    def play(self,show_id,end_callback,ready_callback=None,top=False,command='nil'):
        """ displays the menu 
              end_callback - function to be called when the menu exits
              ready_callback - callback when menu is ready to display (not used)
              top is True when the show is top level (run from [start])
        """
        
        #instantiate arguments
        self.show_id=show_id
        self.end_callback=end_callback
        self.ready_callback=ready_callback
        self.top=top
        self.command=command

        # check  data files are available.
        self.menu_file = self.pp_profile + "/" + self.show_params['medialist']
        if not os.path.exists(self.menu_file):
            self.mon.err(self,"Medialist file not found: "+ self.menu_file)
            self._end('error',"Medialist file not found")
        
        #create a medialist for the menu and read it.
        self.medialist=MediaList()
        if self.medialist.open_list(self.menu_file,self.showlist.sissue()) == False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self._end('error',"Version of medialist different to Pi Presents")
           
        if self.show_params['has-background']=="yes":
            background_index=self.medialist.index_of_track ('pp-menu-background')
            if background_index>=0:
                self.menu_img_file = self.complete_path(self.medialist.track(background_index))
                if not os.path.exists(self.menu_img_file):
                    self.mon.err(self,"Menu background file not found: "+ self.menu_img_file)
                    self._end('error',"Menu background file not found")
            else:
                self.mon.err(self,"Menu background not found in medialist")
                self._end('error',"Menu background not found")

        self.egg_timer=None

        #start timeout alarm if required
        if int(self.show_params['timeout'])<>0:
            self.menu_timeout_running=self.canvas.after(int(self.show_params['timeout'])*1000,self._timeout_menu)
        
        if self.ready_callback<>None:
            self.ready_callback()
        
        self.canvas.delete(ALL)
        
        # display background image
        if self.show_params['has-background']=="yes":
            self._display_background()
 
       #display the list of video titles
        self._display_video_titles()

        # display instructions (hint)
        self.canvas.create_text(int(self.canvas['width'])/2,
                                int(self.canvas['height']) - int(self.show_params['hint-y']),
                                text=self.show_params['hint-text'],
                                fill=self.show_params['hint-colour'],
                                font=self.show_params['hint-font'])
        self.canvas.update( )



   # respond to key presses.
    def key_pressed(self,key_name):
        self.mon.log(self,"received key: " + key_name)
        if self.show_params['disable-controls']=='yes':
            return 
        if key_name=='':
            pass
        
        elif key_name=='escape':
            # if next lower show eor player is running pass down to stop bottom level
            # ELSE stop this show if not at top
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            elif self.player<>None:
                self.player.key_pressed(key_name)
            else:
                # not at top so stop the show
                if  self.top == False:
                    self._end('normal',"exit from stop command")
                else:
                    pass
      
        elif key_name in ('up','down'):
        # if child or sub-show running and is a show pass down
        # if  child not running - move
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            else:
                if self.player==None:
                    if key_name=='up':
                        self._previous()
                    else:
                        self._next()
                
        elif key_name=='return':
            # if child running and is show - pass down
            # if no track already running  - play
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            else:
                if self.player==None:
                    self._play_selected_track(self.medialist.selected_track())

        elif key_name in ('p',' '):
            # pass down if show or track running.
            if self.shower<>None:
                self.shower.key_pressed(key_name)
            elif self.player<>None:
                self.player.key_pressed(key_name)
 
 
    def button_pressed(self,button,edge):
        if button=='play': self.key_pressed("return")
        elif  button =='up': self.key_pressed("up")
        elif button=='down': self.key_pressed("down")
        elif button=='stop': self.key_pressed("escape")
        elif button=='pause': self.key_pressed('p')

        
    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.mon.log(self,"sent terminate to shower")
            self.shower.terminate(reason)
        elif self.player<>None:
            self.mon.log(self,"sent terminate to player")
            self.player.terminate(reason)
        else:
            self._end(reason,'terminated without terminating shower or player')



# *********************
# INTERNAL FUNCTIONS
# ********************

# *********************
# language resources
# *********************

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            # timers may be running so need terminate
            self.terminate("error")
        else:
            return value


# *********************
# Sequencing
# *********************

    def _timeout_menu(self):
        self._end('normal','menu timeout')
        return
        
    
    # finish the player for killing, error or normally
    # this may be called directly sub/child shows or players are not running
    # if they might be running then need to call terminate.
    
    def _end(self,reason,message):
        # self.canvas.delete(ALL)
        # self.canvas.update_idletasks( )
        self.mon.log(self,"Ending menushow: "+ self.show_params['show-ref'])  
        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
        self.end_callback(self.show_id,reason,message)
        self=None
        return


    def _next(self):     
        self._highlight_menu_entry(self.menu_index,False)
        self.medialist.next('ordered')
        if self.menu_index==self.menu_length-1:
            self.menu_index=0
        else:
            self.menu_index+=1
        self._highlight_menu_entry(self.menu_index,True)     


    def _previous(self):   
        self._highlight_menu_entry(self.menu_index,False)
        if self.menu_index==0:
            self.menu_index=self.menu_length-1
        else:
            self.menu_index-=1
        self.medialist.previous('ordered')
        self._highlight_menu_entry(self.menu_index,True)    


# *********************
# Dispatching to Players
# *********************

    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file<>'' and track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to play is: "+ track_file)
        return track_file     
         


    def _play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected track is a dictionary for the track/show
        """
         #remove menu and show working.....        

        if self.menu_timeout_running<>None:
            self.canvas.after_cancel(self.menu_timeout_running)
            self.menu_timeout_running=None
        self.canvas.delete(ALL)
        self._display_eggtimer(self.resource('menushow','m01'))
    
        # dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
        
        if track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.show_id,self.canvas,self.pp_home,self.show_params,selected_track)
            self.player.play(track_file,
                                        self._end_player,
                                        self._delete_eggtimer,
                                        enable_menu=False)
                                        
        elif track_type=="audio":
            # create a audioplayer
            track_file=self.complete_path(selected_track)
            self.player=AudioPlayer(self.show_id,self.canvas,self.pp_home,self.show_params,selected_track)
            self.player.play(track_file,
                                        self._end_player,
                                        self._delete_eggtimer,
                                        enable_menu=False)
                                        
        elif track_type=="image":
            # images played from menus don't have children
            track_file=self.complete_path(selected_track)
            self.player=ImagePlayer(self.show_id,self.canvas,self.pp_home,self.show_params,selected_track)
            self.player.play(track_file,
                                    self._end_player,
                                    self._delete_eggtimer,
                                    enable_menu=False,
                                    )
                                    
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.show_id,self.canvas,self.pp_home,self.show_params,selected_track)
            self.player.play(text,
                                    self._end_player,
                                    self._delete_eggtimer,
                                    enable_menu=False
                                    )
 
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self._end("Unknown show")
            
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self._end_shower,top=False,command='nil')

            elif selected_show['type']=="liveshow":    
                self.shower= LiveShow(selected_show,
                                                                self.canvas,
                                                                self.showlist,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,top=False,command='nil')

            elif selected_show['type']=="menu": 
                self.shower= MenuShow(selected_show,
                                                        self.canvas,
                                                        self.showlist,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self._end_shower,top=False,command='nil')                    
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self._end("Unknown show type")  
                
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self._end("Unknown track type")
    
    # callback from when player ends
    def _end_player(self,reason,message):
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        if reason in("killed","error"):
            self._end(reason,message)
        self._display_eggtimer(self.resource('menushow','m02'))
        self._what_next(message)

    # callback from when shower ends
    def _end_shower(self,show_id,reason,message):
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        if message in ("killed","error"):
            self._end(reason,message)
        self._display_eggtimer(self.resource('menushow','m03'))
        self._what_next(message)  
   

     # at the end of a track just re-display the menu with the original callback from the menu       
    def _what_next(self,message):
        self.mon.log(self,"Re-displaying menu")
        self.play(self.show_id,self.end_callback,top=self.top)



# *********************
# Displaying things
# *********************

    def _display_background(self):
        pil_menu_img=PIL.Image.open(self.menu_img_file)
        # adjust brightness and rotate (experimental)
        # enh=PIL.ImageEnhance.Brightness(pil_menu_img)
        # pil_menu_img=enh.enhance(0.1)
        # pil_menu_img=pil_menu_img.rotate(45)
        self.menu_background = PIL.ImageTk.PhotoImage(pil_menu_img)
        self.drawn = self.canvas.create_image(int(self.canvas['width'])/2,
                                      int(self.canvas['height'])/2,
                                      image=self.menu_background,
                                      anchor=CENTER)


    def _display_video_titles(self):
        self.menu_length=1
        self.menu_entry_id=[]
        x=int(self.show_params['menu-x'])
        y=int(self.show_params['menu-y'])
        self.medialist.start()
        while True:
            id=self.canvas.create_text(x,y,anchor=NW,
                                       text="* "+self.medialist.selected_track()['title'],
                                       fill=self.show_params['entry-colour'],
                                       font=self.show_params['entry-font'])
            self.menu_entry_id.append(id)
            y=y + int(self.show_params['menu-spacing'])
            if self.medialist.at_end():
                break
            self.menu_length+=1
            self.medialist.next('ordered')
            
        # select and highlight the first entry
        self.medialist.start()
        self.menu_index=0
        self._highlight_menu_entry(self.menu_index,True)
        # self.medialist.print_list()

    def _highlight_menu_entry(self,index,state):
        if state==True:
            self.canvas.itemconfig(self.menu_entry_id[index],fill=self.show_params['entry-select-colour'])
        else:
            self.canvas.itemconfig(self.menu_entry_id[index],fill=self.show_params['entry-colour'])
    
    
    def _display_eggtimer(self,text):
        self.egg_timer=self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold")
        self.canvas.update_idletasks( )


    def _delete_eggtimer(self):
        if self.egg_timer!=None:
            self.canvas.delete(self.egg_timer)
Example #53
0
class LiveShow:

    NEW_TRACKS = {
        'image': {
            'title': 'New Image',
            'track-ref': '',
            'type': 'image',
            'location': '',
            'duration': '',
            'transition': '',
            'track-text': '',
            'track-text-font': '',
            'track-text-colour': '',
            'track-text-x': '0',
            'track-text-y': '0'
        },
        'video': {
            'title': 'New Video',
            'track-ref': '',
            'type': 'video',
            'location': '',
            'omx-audio': ''
        }
    }

    IMAGE_FILES = ('.gif', '.jpg', '.jpeg', '.bmp', '.png', '.tif')
    VIDEO_FILES = ('.mp4', '.mkv', '.avi', '.mp2', '.wmv')
    AUDIO_FILES = ('.mp3', '.wav', '.ogg')

    # *******************
    # External interface
    # ********************

    def __init__(self, show, canvas, showlist, pp_home, pp_profile):
        """ canvas - the canvas that the show is to be written on
            showlist - used jus to check the issue of medialist against showlist
            show - the dictionary for the show to be played
            pp_home - Pi presents data_home directory
            pp_profile - Pi presents profile directory
        """

        self.mon = Monitor()
        self.mon.on()

        #instantiate arguments
        self.show = show
        self.showlist = showlist
        self.canvas = canvas
        self.pp_home = pp_home
        self.pp_profile = pp_profile

        # open resources
        self.rr = ResourceReader()

        # Init variables
        self.player = None
        self.shower = None
        self._end_liveshow_signal = False
        self._play_child_signal = False
        self.error = False

        self._livelist = None
        self._new_livelist = None

    def play(self,
             end_callback,
             ready_callback=None,
             top=False,
             command='nil'):
        """ displays the liveshow
              end_callback - function to be called when the liveshow exits
              ready_callback - callback when liveshow is ready to display
              top is True when the show is top level (i.e. run from start show)
        """

        #instantiate the arguments
        self._end_callback = end_callback
        self._ready_callback = ready_callback
        self.top = top
        self.mon.log(self, "Starting show: " + self.show['show-ref'])

        # check  data files are available.
        self.media_file = self.pp_profile + os.sep + self.show['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self, "Medialist file not found: " + self.media_file)
            self._stop("Medialist file not found")

        self.options = command_options()

        self._pp_live_dir1 = self.pp_home + os.sep + 'pp_live_tracks'
        if not os.path.exists(self._pp_live_dir1):
            os.mkdir(self._pp_live_dir1)

        self._pp_live_dir2 = ''
        if self.options['liveshow'] <> "":
            self._pp_live_dir2 = self.options['liveshow']
            if not os.path.exists(self._pp_live_dir2):
                self.mon.err(
                    self,
                    "live tracks directory not found " + self._pp_live_dir2)
                self._end('error', "live tracks directory not found")

        #create a medialist for the liveshow and read it.
        # it should be empty of anonymous tracks but read it to check its version.
        self.medialist = MediaList()
        if self.medialist.open_list(self.media_file,
                                    self.showlist.sissue()) == False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self._end('error', "Version of medialist different to Pi Presents")

        if self.ready_callback <> None:
            self.ready_callback()

        self._play_first_track()

# respond to key presses.

    def key_pressed(self, key_name):
        self.mon.log(self, "received key: " + key_name)

        if key_name == '':
            pass

        elif key_name == 'escape':
            # if next lower show eor player is running pass down to stop the show/track
            # ELSE stop this show except for exceptions
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            elif self.player <> None:
                self.player.key_pressed(key_name)
            else:
                # not at top so stop the show
                if self.top == False:
                    self._stop("exit show to higher level")
                else:
                    pass

        elif key_name in ('up', 'down'):
            # if child or sub-show is running and is a show pass to show, track does not use up/down
            if self.shower <> None:
                self.shower.key_pressed(key_name)

        elif key_name == 'return':
            # if child show or sub-show is running and is show - pass down
            # ELSE use Return to start child
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            else:
                if self.show['has-child'] == "yes":
                    self._play_child()

        elif key_name in ('p', ' '):
            # pass down if show or track running.
            if self.shower <> None:
                self.shower.key_pressed(key_name)
            elif self.player <> None:
                self.player.key_pressed(key_name)

    def button_pressed(self, button, edge):
        if button == 'play': self.key_pressed("return")
        elif button == 'up': self.key_pressed("up")
        elif button == 'down': self.key_pressed("down")
        elif button == 'stop': self.key_pressed("escape")
        elif button == 'pause': self.key_pressed('p')

    # kill or error
    def terminate(self, reason):
        if self.shower <> None:
            self.mon.log(self, "sent terminate to shower")
            self.shower.terminate(reason)
        elif self.player <> None:
            self.mon.log(self, "sent terminate to player")
            self.player.terminate(reason)
        else:
            self._end(reason,
                      'terminated without terminating shower or player')

    def _tidy_up(self):
        pass

    def resource(self, section, item):
        value = self.rr.get(section, item)
        if value == False:
            self.mon.err(self,
                         "resource: " + section + ': ' + item + " not found")
            self.terminate("error", 'Cannot find resource')
        else:
            return value

# ***************************
# Respond to key/button presses
# ***************************

    def _stop(self, message):
        self._end_liveshow_signal = True

    def _play_child(self):
        self._play_child_signal = True
        if self.player <> None:
            self.player.key_pressed("escape")

# ***************************
# end of show functions
# ***************************

    def _end(self, reason, message):
        self._end_liveshow_signal = False
        self.mon.log(self, "Ending Liveshow: " + self.show['show-ref'])
        self._tidy_up()
        self._end_callback(reason, message)
        self = None
        return

    def _nend(self):
        self._end('normal', 'end from state machine')

# ***************************
# Livelist
# ***************************

    def _livelist_add_track(self, afile):
        (root, title) = os.path.split(afile)
        (root, ext) = os.path.splitext(afile)
        if ext.lower() in LiveShow.IMAGE_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['image'], {
                'title': title,
                'track-ref': '',
                'location': afile
            })
        if ext.lower() in LiveShow.VIDEO_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['video'], {
                'title': title,
                'track-ref': '',
                'location': afile
            })
        if ext.lower() in LiveShow.AUDIO_FILES:
            self._livelist_new_track(LiveShow.NEW_TRACKS['video'], {
                'title': title,
                'track-ref': '',
                'location': afile
            })

    def _livelist_new_track(self, fields, values):
        new_track = fields
        self._new_livelist.append(copy.deepcopy(new_track))
        last = len(self._new_livelist) - 1
        self._new_livelist[last].update(values)

    def _new_livelist_create(self):

        self._new_livelist = []
        if os.path.exists(self._pp_live_dir1):
            for file in os.listdir(self._pp_live_dir1):
                file = self._pp_live_dir1 + os.sep + file
                (root_file, ext_file) = os.path.splitext(file)
                if ext_file.lower(
                ) in LiveShow.IMAGE_FILES + LiveShow.VIDEO_FILES + LiveShow.AUDIO_FILES:
                    self._livelist_add_track(file)

        if os.path.exists(self._pp_live_dir2):
            for file in os.listdir(self._pp_live_dir2):
                file = self._pp_live_dir2 + os.sep + file
                (root_file, ext_file) = os.path.splitext(file)
                if ext_file.lower(
                ) in LiveShow.IMAGE_FILES + LiveShow.VIDEO_FILES + LiveShow.AUDIO_FILES:
                    self._livelist_add_track(file)

        self._new_livelist = sorted(
            self._new_livelist,
            key=lambda track: os.path.basename(track['location']).lower())
#       for it in self._new_livelist:
#          print it['location']
#      print ''

    def _livelist_replace_if_changed(self):
        self._new_livelist_create()
        if self._new_livelist <> self._livelist:
            self._livelist = copy.deepcopy(self._new_livelist)
            self._livelist_index = 0

    def _livelist_next(self):
        if self._livelist_index == len(self._livelist) - 1:
            self._livelist_index = 0
        else:
            self._livelist_index += 1

# ***************************
# Play Loop
# ***************************

    def _play_first_track(self):
        self._new_livelist_create()
        self._livelist = copy.deepcopy(self._new_livelist)
        self._livelist_index = 0
        self._play_track()

    def _play_track(self):
        self._livelist_replace_if_changed()
        if len(self._livelist) > 0:
            self._play_selected_track(self._livelist[self._livelist_index])
        else:
            self.display_message(self.canvas, None,
                                 self.resource('liveshow', 'm01'), 5,
                                 self._what_next)

    def _what_next(self):
        # user wants to end
        if self._end_liveshow_signal == True:
            self._end_liveshow_signal = False
            self._end('normal', "show ended by user")

        # play child?
        elif self._play_child_signal == True:
            self._play_child_signal = False
            index = self.medialist.index_of_track('pp-child-show')
            if index >= 0:
                #don't select the track as need to preserve mediashow sequence.
                child_track = self.medialist.track(index)
                self._display_eggtimer(self.resource('liveshow', 'm02'))
                self._play_selected_track(child_track)
            else:
                self.mon.err(
                    self, "Child show not found in medialist: " +
                    self.show['pp-child-show'])
                self._end('error', "child show not found in medialist")

        # otherwise loop to next track
        else:
            self._livelist_next()
            self._play_track()


# ***************************
# Dispatching to Players/Shows
# ***************************

# used to display internal messages in situations where a medialist entry could not be used.

    def display_message(self, canvas, source, content, duration,
                        _display_message_callback):
        self._display_message_callback = _display_message_callback
        tp = {
            'duration': duration,
            'message-colour': 'white',
            'message-font': 'Helvetica 20 bold'
        }
        self.player = MessagePlayer(canvas, tp, tp)
        self.player.play(content, self._display_message_end, None)

    def _display_message_end(self, reason, message):
        self.player = None
        if reason in ("killed", 'error'):
            self._end(reason, message)
        else:
            self._display_message_callback()

    def complete_path(self, selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Track to play is: " + track_file)
        return track_file

    def _play_selected_track(self, selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected_track is a dictionary for the track/show
        """
        # self.canvas.delete(ALL)

        # is menu required
        if self.show['has-child'] == "yes":
            enable_child = True
        else:
            enable_child = False

        #dispatch track by type
        self.player = None
        self.shower = None
        track_type = selected_track['type']
        self.mon.log(self, "Track type is: " + track_type)

        if track_type == "image":
            track_file = self.complete_path(selected_track)
            # images played from menus don't have children
            self.player = ImagePlayer(self.canvas, self.show, selected_track)
            self.player.play(track_file,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=enable_child)
        elif track_type == "video":
            # create a videoplayer
            track_file = self.complete_path(selected_track)
            self.player = VideoPlayer(self.canvas, self.show, selected_track)
            self.player.play(track_file,
                             self.end_player,
                             self.ready_callback,
                             enable_menu=enable_child)

        elif track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >= 0:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
            else:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self._stop("Unknown show")

            if selected_show['type'] == "mediashow":
                self.shower = MediaShow(selected_show, self.canvas,
                                        self.showlist, self.pp_home,
                                        self.pp_profile)
                self.shower.play(self.end_shower, top=False, command='nil')

            elif selected_show['type'] == "menu":
                self.shower = MenuShow(selected_show, self.canvas,
                                       self.showlist, self.pp_home,
                                       self.pp_profile)
                self.shower.play(self.end_shower, top=False, command='nil')

            else:
                self.mon.err(self,
                             "Unknown Show Type: " + selected_show['type'])
                self._stop("Unknown show type")

        else:
            self.mon.err(self, "Unknown Track Type: " + track_type)
            self._stop("Unknown track type")

    def ready_callback(self):
        self._delete_eggtimer()

    def end_player(self, reason, message):
        self.mon.log(self, "Returned from player with message: " + message)
        self.player = None
        if reason in ("killed", "error"):
            self._end(reason, message)
        else:
            self._what_next()

    def end_shower(self, reason, message):
        self.mon.log(self, "Returned from shower with message: " + message)
        self.shower = None
        if reason in ("killed", "error"):
            self._end(reason, message)
        else:
            self._what_next()

    def _display_eggtimer(self, text):
        self.canvas.create_text(int(self.canvas['width']) / 2,
                                int(self.canvas['height']) / 2,
                                text=text,
                                fill='white',
                                font="Helvetica 20 bold")
        self.canvas.update_idletasks()

    def _delete_eggtimer(self):
        self.canvas.delete(ALL)
Example #54
0
class PPIO:
    """
    PPIO provides some IO facilties for Pi presents
     - configures GPIO pins from data in gpio.cfg
     - reads and debounces inputs pins, provides callbacks on state changes which are used to trigger mediashows
     - for output pins allows players to put events, which request the change of state of pins, into a queue. Events are executed at the required time.
    """
 
 
# constants for buttons

# cofiguration from gpio.cfg
    PIN=0                # pin on RPi board GPIO connector e.g. P1-11
    DIRECTION = 1 # IN/OUT/NONE (None is not used)
    NAME = 2      # name for output
    RISING_NAME=3             # name for rising edge callback
    FALLING_NAME=4      # name ofr falling edge callback
    ONE_NAME=5     # name for one state callback
    ZERO_NAME = 6   # name for zero state callback
    REPEAT =  7   #reperat interval for state callbacks (mS)
    THRESHOLD = 8       # threshold of debounce count for state change to be considered
    PULL = 9                  # pull up or down or none
# dynamic data
    COUNT=10          # variable - count of the number of times the input has been 0 (limited to threshold)
    PRESSED = 11      # variable - debounced state 
    LAST = 12       # varible - last state - used to detect edge
    REPEAT_COUNT = 13

    
    TEMPLATE = ['',   #pin
                            '',    # direction
                            '',   #name
                            '','','','',  #input names
                            0,  # repeat
                            0, #threshold
                            '', #pull
                            0,False,False,0]   #dynamics
    
    PINLIST = ('P1-03','P1-05','P1-07','P1-08',
             'P1-10','P1-11','P1-12','P1-13','P1-15','P1-16','P1-18','P1-19',
             'P1-21','P1-22','P1-23','P1-24','P1-26')

    # index of shutdown pin
    SHUTDOWN_INDEX=0
             
# constants for sequencer           
    
    SEQUENCER_PIN = 0         # GPIO pin number, the xx in P1-xx
    SEQUENCER_TO_STATE = 1    # False = off , True =on
    SEQUENCER_TIME = 2        # time since the epoch in seconds
    SEQUENCER_TAG = 3   # tag used to delete all matching event, usually a track reference.

# CLASS VARIABLES
    events=[]
    pins=[]
    last_poll_time=0
    options=None
    # gpio_enabled=False

    
    EVENT_TEMPLATE=[0,False,0,None]

    #executed by main program and by each object using gpio
    def __init__(self):
        self.mon=Monitor()
        self.mon.on()
        self.options=command_options()

     # executed once from main program   
    def init(self,pp_dir,pp_home,pp_profile,widget,button_tick,callback=None):
        
        # instantiate arguments
        self.widget=widget
        self.pp_dir=pp_dir
        self.pp_profile=pp_profile
        self.pp_home=pp_home
        self.button_tick=button_tick
        self.callback=callback

        PPIO.SHUTDOWN_INDEX=0

        # read gpio.cfg file.
        if self.read(self.pp_dir,self.pp_home,self.pp_profile)==False:
            return False

        import RPi.GPIO as GPIO
        self.GPIO = GPIO
        
        #construct the GPIO control list from the configuration
        for index, pin_def in enumerate(PPIO.PINLIST):
            pin=copy.deepcopy(PPIO.TEMPLATE)
            pin_bits = pin_def.split('-')
            pin_num=pin_bits[1:]
            pin[PPIO.PIN]=int(pin_num[0])
            if self.config.has_section(pin_def)==False:
                self.mon.log(self, "no pin definition for "+ pin_def)
                pin[PPIO.DIRECTION]='None'            
            else:
                # unused pin
                if self.config.get(pin_def,'direction')=='none':
                    pin[PPIO.DIRECTION]='none'
                else:
                    pin[PPIO.DIRECTION]=self.config.get(pin_def,'direction')
                    if pin[PPIO.DIRECTION]=='in':
                        # input pin
                        pin[PPIO.RISING_NAME]=self.config.get(pin_def,'rising-name')
                        pin[PPIO.FALLING_NAME]=self.config.get(pin_def,'falling-name')
                        pin[PPIO.ONE_NAME]=self.config.get(pin_def,'one-name')
                        pin[PPIO.ZERO_NAME]=self.config.get(pin_def,'zero-name')
                        if pin[PPIO.FALLING_NAME]=='pp-shutdown':
                            PPIO.SHUTDOWN_INDEX=index
                        if self.config.get(pin_def,'repeat')<>'':
                            pin[PPIO.REPEAT]=int(self.config.get(pin_def,'repeat'))
                        else:
                            pin[PPIO.REPEAT]=-1
                        pin[PPIO.THRESHOLD]=int(self.config.get(pin_def,'threshold'))
                        if self.config.get(pin_def,'pull-up-down')=='up':
                            pin[PPIO.PULL]=GPIO.PUD_UP
                        elif self.config.get(pin_def,'pull-up-down')=='down':
                            pin[PPIO.PULL]=GPIO.PUD_DOWN
                        else:
                            pin[PPIO.PULL]=GPIO.PUD_OFF
                    else:
                        # output pin
                        pin[PPIO.NAME]=self.config.get(pin_def,'name')
 
            # print pin            
            PPIO.pins.append(copy.deepcopy(pin))

        # setup GPIO
        self.GPIO.setwarnings(False)        
        self.GPIO.setmode(self.GPIO.BOARD)
        

        # set up the GPIO inputs and outputs
        for index, pin in enumerate(PPIO.pins):
            num = pin[PPIO.PIN]
            if pin[PPIO.DIRECTION]=='in':
                self.GPIO.setup(num,self.GPIO.IN,pull_up_down=pin[PPIO.PULL])
            elif  pin[PPIO.DIRECTION]=='out':
                self.GPIO.setup(num,self.GPIO.OUT)
                self.GPIO.setup(num,False)
        self.reset_inputs()
        PPIO.gpio_enabled=True

        #init timer
        self.button_tick_timer=None
        PPIO.last_scheduler_time=long(time.time())
        return True

    # called by main program only         
    def poll(self):
        # look at the buttons
        self.do_buttons()

        # kick off output pin sequencer
        poll_time=long(time.time())
        # is current time greater than last time the sceduler was run (previous second or more)
        # run in a loop to catch up because root.after can get behind when images are being rendered etc.
        while PPIO.last_scheduler_time<=poll_time:
            self.do_sequencer(PPIO.last_scheduler_time)
            PPIO.last_scheduler_time +=1
        
        # and loop
        self.button_tick_timer=self.widget.after(self.button_tick,self.poll)


# called by main program only                
    def terminate(self):
        if self.button_tick_timer<>None:
            self.widget.after_cancel(self.button_tick_timer)
        self.clear_events_list(None)
        self.reset_outputs()
        self.GPIO.cleanup()


# ************************************************
# gpio input functions
# called by main program only
# ************************************************
    
    def reset_inputs(self):
        for pin in PPIO.pins:
            pin[PPIO.COUNT]=0
            pin[PPIO.PRESSED]==False
            pin[PPIO.LAST]==False
            pin[PPIO.REPEAT_COUNT]=pin[PPIO.REPEAT]

    # index is of the pins array, provided by the callback ***** needs to be name
    def shutdown_pressed(self):
        if PPIO.SHUTDOWN_INDEX<>0:
            return PPIO.pins[PPIO.SHUTDOWN_INDEX][PPIO.PRESSED]
        else:
            return False

    def do_buttons(self):
        for index, pin in enumerate(PPIO.pins):
            if pin[PPIO.DIRECTION]=='in':
                # debounce
                if self.GPIO.input(pin[PPIO.PIN])==0:
                    if pin[PPIO.COUNT]<pin[PPIO.THRESHOLD]:
                        pin[PPIO.COUNT]+=1
                        if pin[PPIO.COUNT]==pin[PPIO.THRESHOLD]:
                            pin[PPIO.PRESSED]=True
                else: # input us 1
                    if pin[PPIO.COUNT]>0:
                        pin[PPIO.COUNT]-=1
                        if pin[PPIO.COUNT]==0:
                             pin[PPIO.PRESSED]=False
     
                #detect edges
                # falling edge
                if pin[PPIO.PRESSED]==True and pin[PPIO.LAST]==False:
                    pin[PPIO.LAST]=pin[PPIO.PRESSED]
                    pin[PPIO.REPEAT_COUNT]=pin[PPIO.REPEAT]
                    if  pin[PPIO.FALLING_NAME]<>'' and self.callback <> None:
                        self.callback(index, pin[PPIO.FALLING_NAME],"falling")
               #rising edge
                if pin[PPIO.PRESSED]==False and pin[PPIO.LAST]==True:
                    pin[PPIO.LAST]=pin[PPIO.PRESSED]
                    pin[PPIO.REPEAT_COUNT]=pin[PPIO.REPEAT]
                    if  pin[PPIO.RISING_NAME]<>'' and self.callback <> None:
                         self.callback(index, pin[PPIO.RISING_NAME],"rising")

                # do state callbacks
                if pin[PPIO.REPEAT_COUNT]==0:
                    if pin[PPIO.ZERO_NAME]<>'' and pin[PPIO.PRESSED]==True and self.callback<>None:
                        self.callback(index, pin[PPIO.ZERO_NAME],"zero")
                    if pin[PPIO.ONE_NAME]<>'' and pin[PPIO.PRESSED]==False and self.callback<>None:
                        self.callback(index, pin[PPIO.ONE_NAME],"zero")
                    pin[PPIO.REPEAT_COUNT]=pin[PPIO.REPEAT]
                else:
                    if pin[PPIO.REPEAT]<>-1:
                        pin[PPIO.REPEAT_COUNT]-=1

                    
# ************************************************
# gpio output sequencer functions
# ************************************************

    # execute events at the appropriate time and remove from list (runs from main program only)
    # runs through list a number of times because of problems with pop messing up list
    def do_sequencer(self,schedule_time):
        # print 'sequencer run for: ' + str(schedule_time) + ' at ' + str(long(time.time()))
        while True:
            event_found=False
            for index, item in enumerate(PPIO.events):
                if item[PPIO.SEQUENCER_TIME]<=schedule_time:
                    event=PPIO.events.pop(index)
                    event_found=True
                    self.do_event(event[PPIO.SEQUENCER_PIN],event[PPIO.SEQUENCER_TO_STATE],item[PPIO.SEQUENCER_TIME])
                    break
            if event_found==False: break

    # execute an event
    def do_event(self,pin,to_state,req_time):
        self.mon.log (self,'pin P1-'+ str(pin)+ ' set  '+ str(to_state) + ' required: ' + str(req_time)+ ' actual: ' + str(long(time.time())))
        # print 'pin P1-'+ str(pin)+ ' set  '+ str(to_state) + ' required: ' + str(req_time)+ ' actual: ' + str(long(time.time()))
        self.GPIO.output(pin,to_state)

# ************************************************
# gpio output sequencer interface methods
# these can be called from many classes so need to operate on class variables
# ************************************************
    def animate(self,text,tag):
        if self.options['gpio']==True:
            lines = text.split("\n")
            for line in lines:
                error_text=self.parse_animate_fields(line,tag)
                if error_text <>'':
                    return 'error',error_text
            return 'normal',''
        return 'normal',''

    # clear event list
    def clear_events_list(self,tag):
        if self.options['gpio']==True:
            self.mon.log(self,'clear events list ')
            # empty event list
            if tag==None:
                PPIO.events=[]
            else:
                self.remove_events(tag)

    def reset_outputs(self):
        if self.options['gpio']==True:
            self.mon.log(self,'reset outputs')
            for index, pin in enumerate(PPIO.pins):
                num = pin[PPIO.PIN]
                if pin[PPIO.DIRECTION]=='out':
                    self.GPIO.output(num,False)

# ************************************************
# internal functions
# these can be called from many classes so need to operate on class variables
# ************************************************

    def parse_animate_fields(self,line,tag):
        fields= line.split()
        if len(fields)==0:
            return ''
            
        name=fields[0]
        pin= self.pin_of(name)
        if pin ==-1:
            return 'Unknown gpio logical output in: ' + line
       
        to_state_text=fields[1]
        if not (to_state_text  in ('on','off')):
            return 'Illegal to-state in : '+ line
        
        if to_state_text == 'on':
            to_state=True
        else:
            to_state=False
            
        if len(fields)==2:
            delay_text='0'
        else:
            delay_text=fields[2]
        
        if  not delay_text.isdigit():
            return 'Delay is not an integer in : '+ line
        delay=int(delay_text)
        
        self.add_event(pin,to_state,delay,tag)
        # self.print_events()
        return ''

    def pin_of(self,name):
        for pin in PPIO.pins:
            # print " in list" + pin[PPIO.NAME] + str(pin[PPIO.PIN] )
            if pin[PPIO.NAME]==name and pin[PPIO.DIRECTION]=='out':
                return pin[PPIO.PIN]
        return -1

    def print_events(self):
        print
        for i in PPIO.events:
            print i

    def add_event(self,sequencer_pin,sequencer_to_state,sequencer_time,sequencer_tag):
        poll_time=long(time.time())
        # delay is 0 so just do it, don't queue it.
        #if sequencer_time == 0:
            #print "firing now",poll_time
            #self.do_event(sequencer_pin,sequencer_to_state,poll_time)
            #return
        # prepare the event
        event=PPIO.EVENT_TEMPLATE
        event[PPIO.SEQUENCER_PIN]=sequencer_pin
        event[PPIO.SEQUENCER_TO_STATE]=sequencer_to_state
        event[PPIO.SEQUENCER_TIME]=sequencer_time+poll_time+1
        event[PPIO.SEQUENCER_TAG]=sequencer_tag
        # print event
        # find the place in the events list and insert
        # first item in the list is earliest, if two have the same time then last to be added is fired last.
        abs_time=sequencer_time+poll_time
        copy_event= copy.deepcopy(event)
        for index, item in enumerate(PPIO.events):
            if abs_time<item[PPIO.SEQUENCER_TIME]:
                PPIO.events.insert(index,copy_event)
                return copy_event
        PPIO.events.append(copy_event)
        return copy_event
    
    # remove an event not used and does not work
    def remove_event(self,event):
        for index, item in enumerate(PPIO.events):
            if event==item:
                del PPIO.events[index]
                return True
        return False


    # remove all the events with the same tag, usually a track reference
    def remove_events(self,tag):
        left=[]
        for item in PPIO.events:
            if tag<>item[PPIO.SEQUENCER_TAG]:
                left.append(item)
        PPIO.events= left
        #self.print_events()



# ***********************************
# reading gpio.cfg functions
# ************************************

    def read(self,pp_dir,pp_home,pp_profile):
            # try inside profile
            tryfile=pp_profile+os.sep+"gpio.cfg"
            # self.mon.log(self,"Trying gpio.cfg in profile at: "+ tryfile)
            if os.path.exists(tryfile):
                 filename=tryfile
            else:
                # try inside pp_home
                # self.mon.log(self,"gpio.cfg not found at "+ tryfile+ " trying pp_home")
                tryfile=pp_home+os.sep+"gpio.cfg"
                if os.path.exists(tryfile):
                    filename=tryfile
                else:
                    # try inside pipresents
                    # self.mon.log(self,"gpio.cfg not found at "+ tryfile + " trying inside pipresents")
                    tryfile=pp_dir+os.sep+'pp_home'+os.sep+"gpio.cfg"
                    if os.path.exists(tryfile):
                        filename=tryfile
                    else:
                        self.mon.log(self,"gpio.cfg not found at "+ tryfile)
                        self.mon.err(self,"gpio.cfg not found")
                        return False   
            self.config = ConfigParser.ConfigParser()
            self.config.read(filename)
            self.mon.log(self,"gpio.cfg read from "+ filename)
            return True
Example #55
0
class Show(object):

    # ******************************
    # init a show
    # ******************************

    def base__init__(self, show_id, show_params, root, canvas, showlist,
                     pp_dir, pp_home, pp_profile, command_callback):

        # instantiate arguments
        self.show_id = show_id
        self.show_params = show_params
        self.root = root
        self.show_canvas = canvas
        self.canvas = canvas['canvas-obj']
        self.show_canvas_x1 = canvas['show-canvas-x1']
        self.show_canvas_y1 = canvas['show-canvas-y1']
        self.show_canvas_x2 = canvas['show-canvas-x2']
        self.show_canvas_y2 = canvas['show-canvas-y2']
        self.show_canvas_width = canvas['show-canvas-width']
        self.show_canvas_height = canvas['show-canvas-height']
        self.show_canvas_centre_x = canvas['show-canvas-centre-x']
        self.show_canvas_centre_y = canvas['show-canvas-centre-y']
        self.showlist = showlist
        self.pp_dir = pp_dir
        self.pp_home = pp_home
        self.pp_profile = pp_profile
        self.command_callback = command_callback

        # init things that will then be reinitialised by derived classes
        self.medialist = None

        # set up logging
        self.mon = Monitor()
        self.mon.set_log_level(16)

        # create and instance of TimeOfDay scheduler so we can add events
        self.tod = TimeOfDay()

        # create an  instance of showmanager so we can init child/subshows
        self.show_manager = ShowManager(self.show_id, self.showlist,
                                        self.show_params, self.root,
                                        self.show_canvas, self.pp_dir,
                                        self.pp_profile, self.pp_home)

        # init variables
        self.current_player = None
        self.previous_player = None
        self.shower = None
        self.previous_shower = None
        self.user_stop_signal = False
        self.exit_signal = False
        self.terminate_signal = False
        self.show_timeout_signal = False
        self.egg_timer = None
        self.admin_message = None
        self.ending_reason = ''
        self.background_obj = None
        self.background_file = ''
        self.level = 0
        self.subshow_kickback_signal = False
        self.kickback_for_next_track = False

        # get background image from profile.
        # print 'background', self.show_params['background-image']
        if self.show_params['background-image'] != '':
            self.background_file = self.show_params['background-image']

    def base_play(self, end_callback, show_ready_callback,
                  parent_kickback_signal, level, controls_list):
        """ starts the common parts of the show
              end_callback - function to be called when the show exits- callback gets last player of subshow
              show_ready_callback - callback at start to get previous_player
              top is True when the show is top level (run from [start] or by show command from another show)
              direction_command - 'forward' or 'backward' direction to play a subshow
        """
        # instantiate the arguments
        self.end_callback = end_callback
        self.show_ready_callback = show_ready_callback
        self.parent_kickback_signal = parent_kickback_signal
        self.level = level
        # not needed as controls list is not passed down to subshows.
        # self.controls_list=controls_list
        self.mon.trace(
            self,
            self.show_params['show-ref'] + ' at level ' + str(self.level))
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Starting show")

        # check  data files are available.
        if self.show_params['medialist'] == '':
            self.mon.err(self,
                         "Blank Medialist in: " + self.show_params['title'])
            self.end('error',
                     "Blank Medialist in: " + self.show_params['title'])
        self.medialst_file = self.pp_profile + "/" + self.show_params[
            'medialist']
        if not os.path.exists(self.medialst_file):
            self.mon.err(self,
                         "Medialist file not found: " + self.medialst_file)
            self.end('error',
                     "Medialist file not found: " + self.medialst_file)

        # read the medialist for the show
        if self.medialist.open_list(self.medialst_file,
                                    self.showlist.sissue()) is False:
            self.mon.err(self, "Version of medialist different to Pi Presents")
            self.end('error', "Version of medialist different to Pi Presents")

        if self.show_ready_callback is not None:
            # get the previous player from calling show its stored in current because its going to be shuffled before use
            self.previous_shower, self.current_player = self.show_ready_callback(
            )
            self.mon.trace(
                self, ' - previous shower and player is ' +
                self.mon.pretty_inst(self.previous_shower) + ' ' +
                self.mon.pretty_inst(self.current_player))

        #load the show background
        reason, message = Show.base_load_show_background(self)
        if reason == 'error':
            self.mon.err(self, message)
            self.end('error', message)

    # dummy, must be overidden by derived class
    def subshow_ready_callback(self):
        self.mon.err(self, "subshow_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_subshow_ready_callback(self):
        # callback from begining of a subshow, provide previous player to called show
        # used by show_ready_callback of called show
        # in the case of a menushow last track is always the menu
        self.mon.trace(
            self, ' -  sends ' + self.mon.pretty_inst(self.previous_player))
        return self, self.previous_player

    def base_shuffle(self):
        self.previous_player = self.current_player
        self.current_player = None
        self.mon.trace(
            self, ' - LOOP STARTS WITH current is: ' +
            self.mon.pretty_inst(self.current_player))
        self.mon.trace(
            self, '       -  previous is: ' +
            self.mon.pretty_inst(self.previous_player))

    def base_load_track_or_show(self, selected_track, loaded_callback,
                                end_shower_callback, enable_menu):
        track_type = selected_track['type']
        if track_type == "show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index < 0:
                self.mon.err(
                    self, "Show not found in showlist: " +
                    selected_track['sub-show'])
                self.end('error',
                         'show not in showlist: ' + selected_track['sub-show'])
            else:
                self.showlist.select(index)
                selected_show = self.showlist.selected_show()
                self.shower = self.show_manager.init_subshow(
                    self.show_id, selected_show, self.show_canvas)
                self.mon.trace(
                    self, ' - show is: ' + self.mon.pretty_inst(self.shower) +
                    ' ' + selected_show['show-ref'])
                if self.shower is None:
                    self.mon.err(self,
                                 "Unknown Show Type: " + selected_show['type'])
                    self.terminate_signal = True
                    self.what_next_after_showing()
                else:
                    # empty controls list as not used, pleases landscape
                    # print 'send direction to subshow from show',self.kickback_for_next_track
                    # Show.base_withdraw_show_background(self)
                    self.shower.play(end_shower_callback,
                                     self.subshow_ready_callback,
                                     self.kickback_for_next_track,
                                     self.level + 1, [])
        else:
            # dispatch track by type
            self.mon.log(
                self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
                ": Track type is: " + track_type)

            self.current_player = self.base_init_selected_player(
                selected_track)
            #menu has no track file
            if selected_track['type'] == 'menu':
                track_file = ''

            # messageplayer passes the text not a file name
            elif selected_track['type'] == 'message':
                track_file = selected_track['text']
            else:
                track_file = self.base_complete_path(
                    selected_track['location'])

            self.mon.trace(self, ' - track is: ' + track_file)
            self.mon.trace(
                self, ' - current_player is: ' +
                self.mon.pretty_inst(self.current_player))
            self.current_player.load(track_file,
                                     loaded_callback,
                                     enable_menu=enable_menu)

    # DUMMY, must be overidden by derived class
    def what_next_after_showing(self):
        self.mon.err(self, "what_next_after showing not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_init_selected_player(self, selected_track):
        # dispatch track by type
        track_type = selected_track['type']
        self.mon.log(self, "Track type is: " + track_type)

        if track_type == "image":
            return ImagePlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "video":
            return VideoPlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "audio":
            return AudioPlayer(self.show_id, self.showlist, self.root,
                               self.show_canvas, self.show_params,
                               selected_track, self.pp_dir, self.pp_home,
                               self.pp_profile, self.end,
                               self.command_callback)

        elif track_type == "web" and self.show_params['type'] not in (
                'artmediashow', 'artliveshow'):
            return BrowserPlayer(self.show_id, self.showlist, self.root,
                                 self.show_canvas, self.show_params,
                                 selected_track, self.pp_dir, self.pp_home,
                                 self.pp_profile, self.end,
                                 self.command_callback)

        elif track_type == "message":
            return MessagePlayer(self.show_id, self.showlist, self.root,
                                 self.show_canvas, self.show_params,
                                 selected_track, self.pp_dir, self.pp_home,
                                 self.pp_profile, self.end,
                                 self.command_callback)

        elif track_type == "menu":
            return MenuPlayer(self.show_id, self.showlist, self.root,
                              self.show_canvas, self.show_params,
                              selected_track, self.pp_dir, self.pp_home,
                              self.pp_profile, self.end, self.command_callback)

        else:
            return None

    # DUMMY, must be overidden by derived class
    def track_ready_callback(self, track_background):
        self.mon.err(self, "track_ready_callback not overidden")
        # set what to do when closed or unloaded
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    # called just before a track is shown to remove the  previous track from the screen
    # and if necessary close it
    def base_track_ready_callback(self, enable_show_background):
        self.mon.trace(self, '')
        # show the show background done for every track but quick operation
        if enable_show_background is True:
            self.base_show_show_background()
        else:
            self.base_withdraw_show_background()
        # !!!!!!!!! withdraw the background from the parent show
        if self.previous_shower != None:
            self.previous_shower.base_withdraw_show_background()
        # close the player from the previous track
        if self.previous_player is not None:
            self.mon.trace(
                self, ' - hiding previous: ' +
                self.mon.pretty_inst(self.previous_player))
            self.previous_player.hide()
            # print 'Not None  - previous state is',self.previous_player.get_play_state()
            if self.previous_player.get_play_state() == 'showing':
                # print 'showing so closing previous'
                # showing or frozen
                self.mon.trace(
                    self, ' - closing previous: ' +
                    self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_closed_callback_previous)
            else:
                self.mon.trace(self, ' - previous is none\n')
                self.previous_player = None
        self.canvas.update_idletasks()

    def _base_closed_callback_previous(self, status, message):
        self.mon.trace(
            self, ' -  previous is None  - was: ' +
            self.mon.pretty_inst(self.previous_player))
        self.previous_player = None

    # used by end_shower to get the last track of the subshow
    def base_end_shower(self):
        self.mon.trace(self, ' -  returned back to level: ' + str(self.level))
        # get the previous subshow and last track it played
        self.previous_shower, self.current_player = self.shower.base_subshow_ended_callback(
        )
        if self.previous_shower != None:
            self.subshow_kickback_signal = self.shower.subshow_kickback_signal
            # print 'get subshow kickback from subshow',self.subshow_kickback_signal
            self.previous_shower.base_withdraw_show_background()
            self.base_show_show_background()
        self.previous_player = None
        self.mon.trace(
            self, '- get previous_player from subshow: ' +
            self.mon.pretty_inst(self.current_player))
        self.shower = None

    # close or unload the current player when ending the show
    def base_close_or_unload(self):
        self.mon.trace(self, self.mon.pretty_inst(self.current_player))
        # need to test for None because player may be made None by subshow lower down the stack for terminate
        if self.current_player is not None:
            self.mon.trace(self, self.current_player.get_play_state())
            if self.current_player.get_play_state() in ('loaded', 'showing',
                                                        'show-failed'):
                if self.current_player.get_play_state() == 'loaded':
                    self.mon.trace(
                        self,
                        ' - unloading current from: ' + self.ending_reason)
                    self.current_player.unload()
                else:
                    self.mon.trace(
                        self, ' - closing current from: ' + self.ending_reason)
                    self.current_player.close(None)
            self._wait_for_end()
        else:
            # current_player is None because closed further down show stack
            self.mon.trace(
                self, ' - show ended with current_player=None because: ' +
                self.ending_reason)

            # if exiting pipresents then need to close previous show else get memotry leak
            # if not exiting pipresents the keep previous so it can be closed when showing the next track
            # print 'CURRENT PLAYER IS NONE' ,self.ending_reason
            if self.ending_reason == 'killed':
                self.base_close_previous()

            elif self.ending_reason == 'error':
                self.base_close_previous()

            elif self.ending_reason == 'exit':
                self.end('normal', "show quit by exit command")

            elif self.ending_reason == 'user-stop':
                self.end('normal', "show quit by stop operation")

            else:
                self.mon.fatal(
                    self, "Unhandled ending_reason: " + self.ending_reason)
                self.end('error',
                         "Unhandled ending_reason: " + self.ending_reason)

    def _base_closed_callback_current(self, status, message):
        self.mon.trace(
            self, ' current is None  - was: ' +
            self.mon.pretty_inst(self.current_player))

    # wait for unloading or closing to complete then end
    def _wait_for_end(self):
        self.mon.trace(self, self.mon.pretty_inst(self.current_player))
        if self.current_player is not None:
            self.mon.trace(
                self,
                ' - play state is ' + self.current_player.get_play_state())
            if self.current_player.play_state not in ('unloaded', 'closed',
                                                      'load-failed'):  ####
                self.canvas.after(50, self._wait_for_end)
            else:
                self.mon.trace(
                    self, ' - current closed ' +
                    self.mon.pretty_inst(self.current_player) + ' ' +
                    self.ending_reason)

                #why is some of thsi different to close and unload????????????? perhaps because current_player isn't none, just closed
                if self.ending_reason == 'killed':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'error':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'exit':
                    self.current_player.hide()
                    self.current_player = None
                    self.base_close_previous()

                elif self.ending_reason == 'change-medialist':
                    self.current_player.hide()
                    self.current_player = None
                    # self.base_close_previous()
                    # go to start of list via wait for trigger.
                    self.wait_for_trigger()

                elif self.ending_reason == 'show-timeout':
                    self.current_player.hide()
                    self.current_player = None
                    self.end('normal', "show timeout")

                elif self.ending_reason == 'user-stop':
                    if self.level != 0:
                        self.end('normal', "show quit by stop operation")
                    else:
                        self.current_player.hide()
                        self.current_player = None
                        self.base_close_previous()

                else:
                    self.mon.fatal(
                        self, "Unhandled ending_reason: " + self.ending_reason)
                    self.end('error',
                             "Unhandled ending_reason: " + self.ending_reason)
        else:
            self.mon.trace(
                self, ' - current is None ' +
                self.mon.pretty_inst(self.current_player) + ' ' +
                self.ending_reason)

# ***************************
# end of show
# ***************************

# dummy, normally overidden by derived class

    def end(self, reason, message):
        self.mon.err(self, "end not overidden")
        self.base_end('error', message)

    def base_end(self, reason, message):
        self.base_withdraw_show_background()
        self.base_delete_show_background()
        self.mon.trace(
            self, ' at level ' + str(self.level) + '\n - Current is ' +
            self.mon.pretty_inst(self.current_player) + '\n - Previous is ' +
            self.mon.pretty_inst(self.previous_player) + '\n with reason' +
            reason + '\n\n')
        self.mon.log(
            self, self.show_params['show-ref'] + ' Show Id: ' +
            str(self.show_id) + ": Ending Show")
        self.end_callback(self.show_id, reason, message)
        self = None

    def base_subshow_ended_callback(self):
        # called by end_shower of a parent show  to get the last track of the subshow and the subshow
        self.mon.trace(
            self, ' -  returns ' + self.mon.pretty_inst(self.current_player))
        return self, self.current_player

# ********************************
# Respond to external events
# ********************************

    def base_close_previous(self):
        self.mon.trace(self, '')
        # close the player from the previous track
        if self.previous_player is not None:
            self.mon.trace(
                self, ' - previous not None ' +
                self.mon.pretty_inst(self.previous_player))
            if self.previous_player.get_play_state() == 'showing':
                # showing or frozen
                self.mon.trace(
                    self, ' - closing previous ' +
                    self.mon.pretty_inst(self.previous_player))
                self.previous_player.close(self._base_close_previous_callback)
            else:
                self.mon.trace(self, 'previous is not showing')
                self.previous_player.hide()
                self.previous_player = None
                self.end(self.ending_reason, '')
        else:
            self.mon.trace(self, ' - previous is None')
            self.end(self.ending_reason, '')

    def _base_close_previous_callback(self, status, message):
        self.mon.trace(
            self, ' -  previous is None  - was ' +
            self.mon.pretty_inst(self.previous_player))
        self.previous_player.hide()
        self.previous_player = None
        self.end(self.ending_reason, '')

    # exit received from external source
    def base_exit(self):
        self.mon.log(
            self, self.show_params['show-ref'] + ' ' + str(self.show_id) +
            ": Exit received")
        self.mon.trace(self, '')
        # set signal to exit the show when all  sub-shows and players have ended
        self.exit_signal = True
        # then stop subshow or tracks.
        if self.shower is not None:
            self.shower.exit()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal', 'exit by ShowManager')

    # show timeout callback received
    def base_show_timeout_stop(self):
        self.mon.trace(self, '')
        # set signal to exit the show when all  sub-shows and players have ended
        self.show_timeout_signal = True
        # then stop and shows or tracks.
        if self.shower is not None:
            self.shower.show_timeout_stop()
        elif self.current_player is not None:
            self.current_player.input_pressed('stop')
        else:
            self.end('normal', 'stopped by Show Timeout')

    # dummy, normally overidden by derived class
    def terminate(self):
        self.mon.err(self, "terminate not overidden")
        self.base_end('error', "terminate not overidden")

    # terminate Pi Presents
    def base_terminate(self):
        self.mon.trace(self, '')
        # set signal to stop the show when all  sub-shows and players have ended
        self.terminate_signal = True
        if self.shower is not None:
            self.shower.terminate()
        elif self.current_player is not None:
            self.ending_reason = 'killed'
            Show.base_close_or_unload(self)
        else:
            self.end('killed',
                     ' terminated with no shower or player to terminate')

# respond to input events

    def base_handle_input_event(self, symbol):
        self.mon.log(
            self, self.show_params['show-ref'] + ' Show Id: ' +
            str(self.show_id) + ": received input event: " + symbol)

        if self.shower is not None:
            self.shower.handle_input_event(symbol)
        else:
            self.handle_input_event_this_show(symbol)

    #dummy must be overridden in derived class
    def handle_input_event_this_show(self, symbol):
        self.mon.err(self, "input_pressed_this_show not overidden")
        self.ending_reason = 'killed'
        Show.base_close_or_unload(self)

    def base_load_show_background(self):
        # load show background image
        if self.background_file != '':
            background_img_file = self.base_complete_path(self.background_file)
            if not os.path.exists(background_img_file):
                return 'error', "Show background file not found " + background_img_file
            else:
                pil_background_img = Image.open(background_img_file)
                # print 'pil_background_img ',pil_background_img
                image_width, image_height = pil_background_img.size
                window_width = self.show_canvas_width
                window_height = self.show_canvas_height
                if image_width != window_width or image_height != window_height:
                    pil_background_img = pil_background_img.resize(
                        (window_width, window_height))
                self.background = ImageTk.PhotoImage(pil_background_img)
                del pil_background_img
                # print 'self.background ',self.background
                self.background_obj = self.canvas.create_image(
                    self.show_canvas_x1,
                    self.show_canvas_y1,
                    image=self.background,
                    anchor=NW)
                self.canvas.itemconfig(self.background_obj, state='hidden')
                self.canvas.update_idletasks()
                # print '\nloaded background_obj: ',self.background_obj
                return 'normal', 'show background loaded'
        else:
            return 'normal', 'no backgound to load'

    def base_show_show_background(self):
        if self.background_obj is not None:
            # print 'show show background'
            self.canvas.itemconfig(self.background_obj, state='normal')
            # self.canvas.update_idletasks( )

    def base_withdraw_show_background(self):
        self.mon.trace(self, '')
        if self.background_obj is not None:
            # print 'withdraw background obj', self.background_obj
            self.canvas.itemconfig(self.background_obj, state='hidden')
            # self.canvas.update_idletasks( )

    def base_delete_show_background(self):
        if self.background_obj is not None:
            # print 'delete background obj'
            self.canvas.delete(self.background_obj)
            self.background = None
            # self.canvas.update_idletasks( )

# ******************************
# write statiscics
# *********************************

    def write_stats(self, command, show_params, next_track):
        # action, this ref, this name, type, ref, name, location
        if next_track['type'] == 'show':
            # get the show from the showlist
            index = self.showlist.index_of_show(next_track['sub-show'])
            if index < 0:
                self.mon.err(
                    self,
                    "Show not found in showlist: " + next_track['sub-show'])
                self.end('error',
                         'show not in showlist: ' + next_track['sub-show'])
            else:
                target = self.showlist.show(index)
                ref = target['show-ref']
                title = target['title']
                track_type = target['type']
        else:
            # its a track
            ref = next_track['track-ref']
            title = next_track['title']
            track_type = next_track['type']
        if next_track['type'] in ('show', 'message'):
            loc = ''
        else:
            loc = next_track['location']
        self.mon.stats(show_params['type'], show_params['show-ref'],
                       show_params['title'], command, track_type, ref, title,
                       loc)

# ******************************
# lookup controls
# *********************************

    def base_lookup_control(self, symbol, controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        # not found so must be a trigger
        return ''

# ******************************
# Eggtimer
# *********************************

    def display_eggtimer(self):
        text = self.show_params['eggtimer-text']
        if text != '':
            self.egg_timer = self.canvas.create_text(
                int(self.show_params['eggtimer-x']) + self.show_canvas_x1,
                int(self.show_params['eggtimer-y']) + self.show_canvas_y1,
                text=text,
                fill=self.show_params['eggtimer-colour'],
                font=self.show_params['eggtimer-font'],
                anchor='nw')

            self.canvas.update_idletasks()

    def delete_eggtimer(self):
        if self.egg_timer is not None:
            self.canvas.delete(self.egg_timer)
            self.egg_timer = None
            self.canvas.update_idletasks()

# ******************************
# Display Admin Messages
# *********************************

    def display_admin_message(self, text):

        self.admin_message = self.canvas.create_text(
            int(self.show_params['admin-x']) + self.show_canvas_x1,
            int(self.show_params['admin-y']) + self.show_canvas_y1,
            text=text,
            fill=self.show_params['admin-colour'],
            font=self.show_params['admin-font'],
            anchor='nw')

        self.canvas.update_idletasks()

    def delete_admin_message(self):
        if self.admin_message is not None:
            self.canvas.delete(self.admin_message)
            self.canvas.update_idletasks()

# ******************************
# utilities
# ******************************

    def base_complete_path(self, track_file):
        #  complete path of the filename of the selected entry
        if track_file != '' and track_file[0] == "+":
            track_file = self.pp_home + track_file[1:]
        self.mon.log(self, "Track to load is: " + track_file)
        return track_file

    def calculate_duration(self, line):
        fields = line.split(':')
        if len(fields) == 1:
            secs = fields[0]
            minutes = '0'
            hours = '0'
        if len(fields) == 2:
            secs = fields[1]
            minutes = fields[0]
            hours = '0'
        if len(fields) == 3:
            secs = fields[2]
            minutes = fields[1]
            hours = fields[0]
        if not secs.isdigit() or not minutes.isdigit() or not hours.isdigit():
            return 'error', 'bad time: ' + line, 0
        else:
            return 'normal', '', 3600 * long(hours) + 60 * long(
                minutes) + long(secs)
class LiveShow:
    """ plays a set of tracks the content of which is dynamically specified by plaacing track files
                in one of two directories. Tracks are played in file leafname alphabetical order.
                Can be interrupted
    """
            
# *******************
# External interface
# ********************

    def __init__(self,
                            show_params,
                             root,
                            canvas,
                            showlist,
                             pp_dir,
                            pp_home,
                            pp_profile):

        self.mon=Monitor()
        self.mon.on()
        
        #instantiate arguments
        self.show_params =show_params
        self.showlist=showlist
        self.root=root
        self.canvas=canvas
        self.pp_dir=pp_dir
        self.pp_home=pp_home
        self.pp_profile=pp_profile

        # open resources
        self.rr=ResourceReader()

        #create and instance of TimeOfDay scheduler so we can add events
        self.tod=TimeOfDay()


        # Init variables
        self.player=None
        self.shower=None
        self.end_liveshow_signal=False
        self.end_trigger_signal= False
        self.play_child_signal = False
        self.error=False
        self.egg_timer=None
        self.duration_timer=None
        self.state='closed'
        self.livelist=None
        self.new_livelist= None



    def play(self,show_id,end_callback,ready_callback, top=False,command='nil'):
	global defaultDur
	if defaultDur == None:
		defaultDur = self.showlist.get_dur()

        #instantiate the arguments
        self.show_id=show_id
        self.end_callback=end_callback
        self.ready_callback=ready_callback
        self.top=top
        self.mon.log(self,"Starting show: " + self.show_params['show-ref'])



        # check  data files are available.
        self.media_file = self.pp_profile + os.sep + self.show_params['medialist']
        if not os.path.exists(self.media_file):
            self.mon.err(self,"Medialist file not found: "+ self.media_file)
            self.end_liveshow_signal=True

            
        self.options=command_options()
               
        self.pp_live_dir1 = self.pp_home + os.sep + 'pp_live_tracks'
        if not os.path.exists(self.pp_live_dir1):
            os.mkdir(self.pp_live_dir1)
            os.mkdir(self.pp_live_dir1+os.sep+ 'Archive')

        self.pp_live_dir2=''   
        if self.options['liveshow'] <>"":
            self.pp_live_dir2 = self.options['liveshow']
            if not os.path.exists(self.pp_live_dir2):
                self.mon.err(self,"live tracks directory not found " + self.pp_live_dir2)
                self.end('error',"live tracks directory not found")

        #create a medialist for the liveshow and read it.
        # it should be empty of anonymous tracks but read it to check its version.
        self.medialist=MediaList()

        if self.medialist.open_list(self.media_file,self.showlist.sissue())==False:
            self.mon.err(self,"Version of medialist different to Pi Presents")
            self.end('error',"Version of medialist different to Pi Presents")

        #get control bindings for this show if top level
        controlsmanager=ControlsManager()
        if self.top==True:
            self.controls_list=controlsmanager.default_controls()
            # and merge in controls from profile
            self.controls_list=controlsmanager.merge_show_controls(self.controls_list,self.show_params['controls'])

        #set up the time of day triggers for the show
        if self.show_params['trigger-start']in('time','time-quiet'):
            error_text=self.tod.add_times(self.show_params['trigger-start-time'],id(self),self.tod_start_callback,self.show_params['trigger-start'])
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)
                
        if self.show_params['trigger-end']=='time':
            error_text=self.tod.add_times(self.show_params['trigger-end-time'],id(self),self.tod_end_callback,'n/a')
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)

        if self.show_params['trigger-end']=='duration':
            error_text=self.calculate_duration(self.show_params['trigger-end-time'])
            if error_text<>'':
                self.mon.err(self,error_text)
                self.end('error',error_text)       

        self.wait_for_trigger()                


    def managed_stop(self):
        # if next lower show eor player is running pass down to stop the show/track
        if self.shower<>None:
            self.shower.managed_stop()
        else:
            self.end_liveshow_signal=True
            if self.player<>None:
                self.player.input_pressed('stop')

                
    # kill or error
    def terminate(self,reason):
        if self.shower<>None:
            self.shower.terminate(reason)
        elif self.player<>None:
            self.player.terminate(reason)
        else:
            self.end(reason,'terminated without terminating shower or player')

 
   # respond to key presses.
    def input_pressed(self,symbol,edge,source):
        self.mon.log(self,"received key: " + symbol)
        if self.show_params['disable-controls']=='yes':
            return 

       # if at top convert symbolic name to operation otherwise lower down we have received an operation
        # look through list of standard symbols to find match (symbolic-name, function name) operation =lookup (symbol
        if self.top==True:
            operation=self.lookup_control(symbol,self.controls_list)
        else:
            operation=symbol
        # print 'operation',operation
        # if no match for symbol against standard operations then return
        if operation=='':
            return

        else:
            #service the standard inputs for this show
            if operation=='stop':
                # if next lower show eor player is running pass down to stop the show/track
                # ELSE stop this show except for exceptions
                if self.shower<>None:
                    self.shower.input_pressed('stop',edge,source)
                elif self.player<>None:
                    self.player.input_pressed('stop')
                else:
                    # not at top so stop the show
                    if  self.top == False:
                        self.end_liveshow_signal=True
                    else:
                        pass
        
            elif operation in ('up','down'):
            # if child or sub-show is running and is a show pass to show, track does not use up/down
                if self.shower<>None:
                    self.shower.input_pressed(operation,edge,source)

                    
            elif operation=='play':
                # if child show or sub-show is running and is show - pass down
                # ELSE use Return to start child
                if self.shower<>None:
                    self.shower.input_pressed(operation,edge,source)
                else:
                    if self.show_params['has-child']=="yes":
                        self.play_child_signal=True
                        if self.player<>None:
                            self.player.input_pressed("stop")
                  
            elif operation == 'pause':
                # pass down if show or track running.
                if self.shower<>None:
                    self.shower.input_pressed(operation,edge,source)
                elif self.player<>None:
                    self.player.input_pressed(operation)

            elif operation[0:4]=='omx-' or operation[0:6]=='mplay-':
                if self.player<>None:
                    self.player.input_pressed(operation)
     
    def lookup_control(self,symbol,controls_list):
        for control in controls_list:
            if symbol == control[0]:
                return control[1]
        return ''

# ***************************
# Constructing Livelist
# ***************************       
        
    def livelist_add_track(self,afile):

        (root,title)=os.path.split(afile)
        (root_plus,ext)= os.path.splitext(afile)
        if ext.lower() in PPdefinitions.IMAGE_FILES:
            self.livelist_new_track(PPdefinitions.new_tracks['image'],{'title':title,'track-ref':'','location':afile})
        if ext.lower() in PPdefinitions.VIDEO_FILES:
            self.livelist_new_track(PPdefinitions.new_tracks['video'],{'title':title,'track-ref':'','location':afile})
        if ext.lower() in PPdefinitions.AUDIO_FILES:
            self.livelist_new_track(PPdefinitions.new_tracks['audio'],{'title':title,'track-ref':'','location':afile})
        if ext.lower() in PPdefinitions.WEB_FILES:
            self.livelist_new_track(PPdefinitions.new_tracks['web'],{'title':title,'track-ref':'','location':afile})
        if ext.lower()=='.cfg':
            self.livelist_new_plugin(afile,title)
           

    def livelist_new_plugin(self,plugin_cfg,title):

        # read the file which is a plugin cfg file into a dictionary
        self.plugin_config = ConfigParser.ConfigParser()
        self.plugin_config.read(plugin_cfg)
        self.plugin_params =  dict(self.plugin_config.items('plugin'))
        # create a new livelist entry of a type specified in the config file with plugin
        self.livelist_new_track(PPdefinitions.new_tracks[self.plugin_params['type']],{'title':title,'track-ref':'','plugin':plugin_cfg,'location':plugin_cfg})        

        
    def livelist_new_track(self,fields,values):
        new_track=fields
        self.new_livelist.append(copy.deepcopy(new_track))
        last = len(self.new_livelist)-1
        self.new_livelist[last].update(values)        
    

        
    def new_livelist_create(self):
     
        self.new_livelist=[]
        if os.path.exists(self.pp_live_dir1):
            for file in os.listdir(self.pp_live_dir1):
                file = self.pp_live_dir1 + os.sep + file
                (root_file,ext_file)= os.path.splitext(file)
                if (ext_file.lower() in PPdefinitions.IMAGE_FILES+PPdefinitions.VIDEO_FILES+PPdefinitions.AUDIO_FILES+PPdefinitions.WEB_FILES) or (ext_file.lower()=='.cfg'):
                    self.livelist_add_track(file)
                    
        if os.path.exists(self.pp_live_dir2):
            for file in os.listdir(self.pp_live_dir2):
                file = self.pp_live_dir2 + os.sep + file
                (root_file,ext_file)= os.path.splitext(file)
                if ext_file.lower() in PPdefinitions.IMAGE_FILES+PPdefinitions.VIDEO_FILES+PPdefinitions.AUDIO_FILES+PPdefinitions.WEB_FILES or (ext_file.lower()=='.cfg'):
                    self.livelist_add_track(file)
                    

        self.new_livelist= sorted(self.new_livelist, key= lambda track: os.path.basename(track['location']).lower())
        # print 'LIVELIST'
        # for it in self.new_livelist:
            # print 'type: ', it['type'], 'loc: ',it['location'],'\nplugin cfg: ', it['plugin']
        # print ''


    
    def livelist_replace_if_changed(self):
        self.new_livelist_create()
        if  self.new_livelist<>self.livelist:
            self.livelist=copy.deepcopy(self.new_livelist)
            self.livelist_index = 1
   
   
    def livelist_next(self):

	skip = False
        if self.livelist_index== len(self.livelist)-1:
            self.livelist_index=0
        else:
            self.livelist_index +=1

	#Author Joe Houng 


	#get properties from file name if it exists
	runningFileName = self.livelist[self.livelist_index]['title']
	fileNameTupel = ProcessFileName(runningFileName)
	dur = fileNameTupel[0]
	startDate = fileNameTupel[1]
	endDate = fileNameTupel[2]

	if dur == "":
		#duration not specified in filename
		global defaultDur
		dur = defaultDur

	if startDate != "":
		curDate = time.strftime('%Y-%m-%d-%H-%M-%S')
		if startDate > curDate:
			print dur
			self.livelist_index +=1
			skip = True
			dur = defaultDur

	if skip == False:
	        if endDate != "":
			if endDate <= time.strftime('%Y-%m-%d-%H-%M-%S'):
				try:
					toArchive(runningFileName, self.pp_home)
				except IOError:
					None
	self.showlist.assign_dur(dur);
	skip = False


# ***************************
# Sequencing
# ***************************


    def wait_for_trigger(self):
        self.state='waiting'
        if self.ready_callback<>None:
            self.ready_callback()

        self.mon.log(self,"Waiting for trigger: "+ self.show_params['trigger-start'])

        if self.show_params['trigger-start'] in ('time','time-quiet'):
            # if next show is this one display text
            next_show=self.tod.next_event_time()
            if next_show[3]<>True:
                if next_show[1]=='tomorrow':
                    text = self.resource('liveshow','m04')
                else:
                    text = self.resource('liveshow','m03')                     
                text=text.replace('%tt',next_show[0])
                self.display_message(self.canvas,'text',text,0,self.play_first_track)  
            
        elif self.show_params['trigger-start']=="start":
            self.play_first_track()            
        else:
            self.mon.err(self,"Unknown trigger: "+ self.show_params['trigger-start'])
            self.end('error',"Unknown trigger type")

    # callbacks from time of day scheduler
    def tod_start_callback(self):
        if self.state=='waiting' and self.show_params['trigger-start']in('time','time-quiet'):
            self.play_first_track()      

    def tod_end_callback(self):
        if self.state=='playing' and self.show_params['trigger-end'] in ('time','duration'):
            self.end_trigger_signal=True
            if self.shower<>None:
                self.shower.input_pressed('stop','front','')
            elif self.player<>None:
                self.player.input_pressed('stop')

    def play_first_track(self):
        self.state='playing'
        skip = False
        # start duration timer
        if self.show_params['trigger-end']=='duration':
            # print 'set alarm ', self.duration
            self.duration_timer = self.canvas.after(self.duration*1000,self.tod_end_callback)
        self.new_livelist_create()
        self.livelist = copy.deepcopy(self.new_livelist)
        self.livelist_index = 0
	
	#Author Joe Houng

	#get properties from file name if it exists
	runningFileName = self.livelist[self.livelist_index]['title']

	fileNameTupel = ProcessFileName(runningFileName)
	dur = fileNameTupel[0]
	startDate = fileNameTupel[1]
	endDate = fileNameTupel[2]
	

	if dur == "":
		#duration not specified in filename
		global defaultDur
		dur = defaultDur
		
	if startDate != "":
		curDate = time.strftime('%Y-%m-%d')
		if startDate > curDate:
			print dur
			self.livelist_index +=1
			skip = True

	if skip == False:
		if endDate != "":
			if endDate <= time.strftime('%Y-%m-%d'):
				toArchive(runningFileName)
		self.showlist.assign_dur(dur);
	skip = False

	
	
	self.play_track()




        
    def play_track(self):        
        self.livelist_replace_if_changed()
        if len(self.livelist)>0:
            self.play_selected_track(self.livelist[self.livelist_index])
        else:
            self.display_message(self.canvas,None,self.resource('liveshow','m01'),5,self.what_next)


     
    def what_next(self):
        # end of show time trigger
        if self.end_trigger_signal==True:
            self.end_trigger_signal=False
            if self.top==True:
                self.state='waiting'
                self.wait_for_trigger()
            else:
                # not at top so stop the show
                self.end('normal','sub-show end time trigger')
                    
        # user wants to end 
        elif self.end_liveshow_signal==True:
            self.end_liveshow_signal=False
            self.end('normal',"show ended by user")
        
        # play child?
        elif self.play_child_signal == True:
            self.play_child_signal=False
            index = self.medialist.index_of_track('pp-child-show')
            if index >=0:
                #don't select the track as need to preserve mediashow sequence.
                child_track=self.medialist.track(index)
                self.display_eggtimer(self.resource('liveshow','m02'))
                self.play_selected_track(child_track)
            else:
                self.mon.err(self,"Child show not found in medialist: "+ self.show_params['pp-child-show'])
                self.end('error',"child show not found in medialist")
                
        # otherwise loop to next track                       
        else:
            self.livelist_next()
            self.play_track()
          
      
# ***************************
# Dispatching to Players/Shows 
# ***************************

    def ready_callback(self):
        self.delete_eggtimer()

    def play_selected_track(self,selected_track):
        """ selects the appropriate player from type field of the medialist and computes
              the parameters for that type
              selected_track is a dictionary for the track/show
        """
        self.canvas.delete('pp-content')
        
        # is menu required
        if self.show_params['has-child']=="yes":
            enable_child=True
        else:
            enable_child=False

        #dispatch track by type
        self.player=None
        self.shower=None
        track_type = selected_track['type']
        self.mon.log(self,"Track type is: "+ track_type)
                                      
        if track_type=="image":
            track_file=self.complete_path(selected_track)
            # images played from menus don't have children
            self.player=ImagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                    self.showlist,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=enable_child)
            
        elif track_type=="video":
            # create a videoplayer
            track_file=self.complete_path(selected_track)
            self.player=VideoPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=enable_child)
                   
        elif track_type=="audio":
            # create a audioplayer
            track_file=self.complete_path(selected_track)
            self.player=AudioPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=enable_child)
                                                
        elif track_type=="message":
            # bit odd because MessagePlayer is used internally to display text. 
            text=selected_track['text']
            self.player=MessagePlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(text,
                                    self.showlist,
                                    self.end_player,
                                    self.ready_callback,
                                    enable_menu=enable_child
                                    )

        elif track_type=="web":
            # create a browser
            track_file=self.complete_path(selected_track)
            self.player=BrowserPlayer(self.show_id,self.root,self.canvas,self.show_params,selected_track,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(track_file,
                                        self.showlist,
                                        self.end_player,
                                        self.ready_callback,
                                        enable_menu=enable_child)
                                        
        elif track_type=="show":
            # get the show from the showlist
            index = self.showlist.index_of_show(selected_track['sub-show'])
            if index >=0:
                self.showlist.select(index)
                selected_show=self.showlist.selected_show()
            else:
                self.mon.err(self,"Show not found in showlist: "+ selected_track['sub-show'])
                self.end_liveshow_signal=True
                
            if selected_show['type']=="mediashow":    
                self.shower= MediaShow(selected_show,
                                                               self.root,
                                                                self.canvas,
                                                                self.showlist,
                                                               self.pp_dir,
                                                                self.pp_home,
                                                                self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            
            elif selected_show['type']=="menu":
                self.shower= MenuShow(selected_show,
                                                          self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                          self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="radiobuttonshow":
                self.shower= RadioButtonShow(selected_show,
                                                         self.root,
                                                        self.canvas,
                                                        self.showlist,
                                                         self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')

            elif selected_show['type']=="hyperlinkshow":
                self.shower= HyperlinkShow(selected_show,
                                                       sef.root,
                                                        self.canvas,
                                                        self.showlist,
                                                       self.pp_dir,
                                                        self.pp_home,
                                                        self.pp_profile)
                self.shower.play(self.show_id,self.end_shower,self.ready_callback,top=False,command='nil')
                
            else:
                self.mon.err(self,"Unknown Show Type: "+ selected_show['type'])
                self.end_liveshow_signal=True
                                                                            
        else:
            self.mon.err(self,"Unknown Track Type: "+ track_type)
            self.end_liveshow_signal=True
            

    def end_shower(self,show_id,reason,message):
        self.mon.log(self,"Returned from shower with message: "+ message)
        self.shower=None
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            self.what_next()  
        
    def end_player(self,reason,message):
        self.mon.log(self,"Returned from player with message: "+ message)
        self.player=None
        if reason in("killed","error"):
            self.end(reason,message)
        else:
            self.what_next()


# ***************************
# end of show 
# ***************************

    def end(self,reason,message):
        self.end_liveshow_signal=False
        self.mon.log(self,"Ending Liveshow: "+ self.show_params['show-ref'])
        self.tidy_up()
        self.end_callback(self.show_id,reason,message)
        self=None

    
    def tidy_up(self):
        if self.duration_timer<>None:
            self.canvas.after_cancel(self.duration_timer)
            self.duration_timer=None
        #clear outstanding time of day events for this show
        # self.tod.clear_times_list(id(self))     


# ******************************
# Displaying things
# *********************************
        
    def display_eggtimer(self,text):
        self.egg_timer=self.canvas.create_text(int(self.canvas['width'])/2,
                                              int(self.canvas['height'])/2,
                                                  text= text,
                                                  fill='white',
                                                  font="Helvetica 20 bold",
                                                   tag='pp-eggtimer')
        self.canvas.update_idletasks( )


    def delete_eggtimer(self):
        self.canvas.delete('pp-eggtimer')
        self.canvas.update_idletasks( )

    # used to display internal messages in situations where a medialist entry could not be used.
    def display_message(self,canvas,source,content,duration,display_message_callback):
            self.display_message_callback=display_message_callback
            tp={'duration':duration,'message-colour':'white','message-font':'Helvetica 20 bold','message-justify':'left',
                'background-colour':'','background-image':'','show-control-begin':'','show-control-end':'',
                'animate-begin':'','animate-clear':'','animate-end':'','message-x':'','message-y':'',
                'display-show-background':'no','display-show-text':'no','show-text':'','track-text':'',
                'plugin':''}
            self.player=MessagePlayer(self.show_id,self.root,canvas,tp,tp,self.pp_dir,self.pp_home,self.pp_profile)
            self.player.play(content,self.showlist,self.display_message_end,None)

   
    def  display_message_end(self,reason,message):
        self.player=None
        if reason in ("killed",'error'):
            self.end(reason,message)
        else:
            self.display_message_callback()


# ******************************
# utilities
# *********************************

    def resource(self,section,item):
        value=self.rr.get(section,item)
        if value==False:
            self.mon.err(self, "resource: "+section +': '+ item + " not found" )
            self.terminate("error",'Cannot find resource')
        else:
            return value
        

    def complete_path(self,selected_track):
        #  complete path of the filename of the selected entry
        track_file = selected_track['location']
        if track_file<>'' and track_file[0]=="+":
                track_file=self.pp_home+track_file[1:]
        self.mon.log(self,"Track to play is: "+ track_file)
        return track_file     
         

    def calculate_duration(self,line):
        fields=line.split(':')
        if len(fields)==1:
            secs=fields[0]
            minutes='0'
            hours='0'
        if len(fields)==2:
            secs=fields[1]
            minutes=fields[0]
            hours='0'
        if len(fields)==3:
            secs=fields[2]
            minutes=fields[1]
            hours=fields[0]
        self.duration=3600*long(hours)+60*long(minutes)+long(secs)
        return ''
Example #57
-1
class PiPresents(object):

    def pipresents_version(self):
        vitems=self.pipresents_issue.split('.')
        if len(vitems)==2:
            # cope with 2 digit version numbers before 1.3.2
            return 1000*int(vitems[0])+100*int(vitems[1])
        else:
            return 1000*int(vitems[0])+100*int(vitems[1])+int(vitems[2])


    def __init__(self):
        # gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_INSTANCES|gc.DEBUG_OBJECTS|gc.DEBUG_SAVEALL)
        gc.set_debug(gc.DEBUG_UNCOLLECTABLE|gc.DEBUG_SAVEALL)
        self.pipresents_issue="1.3.5"
        self.pipresents_minorissue = '1.3.5d'
        # position and size of window without -f command line option
        self.nonfull_window_width = 0.45 # proportion of width
        self.nonfull_window_height= 0.7 # proportion of height
        self.nonfull_window_x = 0 # position of top left corner
        self.nonfull_window_y=0   # position of top left corner


        StopWatch.global_enable=False

        # set up the handler for SIGTERM
        signal.signal(signal.SIGTERM,self.handle_sigterm)
        

# ****************************************
# Initialisation
# ***************************************
        # get command line options
        self.options=command_options()

        # get Pi Presents code directory
        pp_dir=sys.path[0]
        self.pp_dir=pp_dir
        
        if not os.path.exists(pp_dir+"/pipresents.py"):
            if self.options['manager']  is False:
                tkMessageBox.showwarning("Pi Presents","Bad Application Directory")
            exit(102)

        
        # Initialise logging and tracing
        Monitor.log_path=pp_dir
        self.mon=Monitor()
        # Init in PiPresents only
        self.mon.init()

        # uncomment to enable control of logging from within a class
        # Monitor.enable_in_code = True # enables control of log level in the code for a class  - self.mon.set_log_level()

        
        # make a shorter list to log/trace only some classes without using enable_in_code.
        Monitor.classes  = ['PiPresents',
                            
                            'HyperlinkShow','RadioButtonShow','ArtLiveShow','ArtMediaShow','MediaShow','LiveShow','MenuShow',
                            'GapShow','Show','ArtShow',
                            'AudioPlayer','BrowserPlayer','ImagePlayer','MenuPlayer','MessagePlayer','VideoPlayer','Player',
                            'MediaList','LiveList','ShowList',
                            'PathManager','ControlsManager','ShowManager','PluginManager','IOPluginManager',
                            'MplayerDriver','OMXDriver','UZBLDriver',
                            'TimeOfDay','ScreenDriver','Animate','OSCDriver','CounterManager',
                            'Network','Mailer'
                            ]
        

        # Monitor.classes=['PiPresents','MediaShow','GapShow','Show','VideoPlayer','Player','OMXDriver']
        # Monitor.classes=['OSCDriver']
        
        # get global log level from command line
        Monitor.log_level = int(self.options['debug'])
        Monitor.manager = self.options['manager']
        # print self.options['manager']
        self.mon.newline(3)
        self.mon.sched (self,None, "Pi Presents is starting, Version:"+self.pipresents_minorissue + ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, "Pi Presents is starting, Version:"+self.pipresents_minorissue+ ' at '+time.strftime("%Y-%m-%d %H:%M.%S"))
        # self.mon.log (self," OS and separator:" + os.name +'  ' + os.sep)
        self.mon.log(self,"sys.path[0] -  location of code: "+sys.path[0])

        # log versions of Raspbian and omxplayer, and GPU Memory
        with open("/boot/issue.txt") as ifile:
            self.mon.log(self,'\nRaspbian: '+ifile.read())

        self.mon.log(self,'\n'+check_output(["omxplayer", "-v"]))
        self.mon.log(self,'\nGPU Memory: '+check_output(["vcgencmd", "get_mem", "gpu"]))

        if os.geteuid() == 0:
            print 'Do not run Pi Presents with sudo'
            self.mon.log(self,'Do not run Pi Presents with sudo')
            self.mon.finish()
            sys.exit(102)

        
        if "DESKTOP_SESSION" not in os.environ:
            print 'Pi Presents must be run from the Desktop'
            self.mon.log(self,'Pi Presents must be run from the Desktop')
            self.mon.finish()
            sys.exit(102)
        else:
            self.mon.log(self,'Desktop is '+ os.environ['DESKTOP_SESSION'])
        
        # optional other classes used
        self.root=None
        self.ppio=None
        self.tod=None
        self.animate=None
        self.ioplugin_manager=None
        self.oscdriver=None
        self.osc_enabled=False
        self.tod_enabled=False
        self.email_enabled=False
        
        user=os.getenv('USER')

        if user is None:
            tkMessageBox.showwarning("You must be logged in to run Pi Presents")
            exit(102)

        if user !='pi':
            self.mon.warn(self,"You must be logged as pi to use GPIO")

        self.mon.log(self,'User is: '+ user)
        # self.mon.log(self,"os.getenv('HOME') -  user home directory (not used): " + os.getenv('HOME')) # does not work
        # self.mon.log(self,"os.path.expanduser('~') -  user home directory: " + os.path.expanduser('~'))   # does not work



        # check network is available
        self.network_connected=False
        self.network_details=False
        self.interface=''
        self.ip=''
        self.unit=''
        
        # sets self.network_connected and self.network_details
        self.init_network()

        
        # start the mailer and send email when PP starts
        self.email_enabled=False
        if self.network_connected is True:
            self.init_mailer()
            if self.email_enabled is True and self.mailer.email_at_start is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Started on ' + time.strftime("%Y-%m-%d %H:%M")
                message = time.strftime("%Y-%m-%d %H:%M") + '\nUnit: ' + self.unit + '   Profile: '+ self.options['profile']+ '\n ' + self.interface + '\n ' + self.ip 
                self.send_email('start',subject,message) 

         
        # get profile path from -p option
        if self.options['profile'] != '':
            self.pp_profile_path="/pp_profiles/"+self.options['profile']
        else:
            self.mon.err(self,"Profile not specified in command ")
            self.end('error','Profile not specified with the commands -p option')
        
       # get directory containing pp_home from the command,
        if self.options['home']  == "":
            home = os.sep+ 'home' + os.sep + user + os.sep+"pp_home"
        else:
            home = self.options['home'] + os.sep+ "pp_home"         
        self.mon.log(self,"pp_home directory is: " + home)


        # check if pp_home exists.
        # try for 10 seconds to allow usb stick to automount
        found=False
        for i in range (1, 10):
            self.mon.log(self,"Trying pp_home at: " + home +  " (" + str(i)+')')
            if os.path.exists(home):
                found=True
                self.pp_home=home
                break
            time.sleep (1)
        if found is True:
            self.mon.log(self,"Found Requested Home Directory, using pp_home at: " + home)
        else:
            self.mon.err(self,"Failed to find pp_home directory at " + home)
            self.end('error',"Failed to find pp_home directory at " + home)


        # check profile exists
        self.pp_profile=self.pp_home+self.pp_profile_path
        if os.path.exists(self.pp_profile):
            self.mon.sched(self,None,"Running profile: " + self.pp_profile_path)
            self.mon.log(self,"Found Requested profile - pp_profile directory is: " + self.pp_profile)
        else:
            self.mon.err(self,"Failed to find requested profile: "+ self.pp_profile)
            self.end('error',"Failed to find requested profile: "+ self.pp_profile)

        self.mon.start_stats(self.options['profile'])
        
        if self.options['verify'] is True:
            self.mon.err(self,"Validation option not supported - use the editor")
            self.end('error','Validation option not supported - use the editor')

         
        # initialise and read the showlist in the profile
        self.showlist=ShowList()
        self.showlist_file= self.pp_profile+ "/pp_showlist.json"
        if os.path.exists(self.showlist_file):
            self.showlist.open_json(self.showlist_file)
        else:
            self.mon.err(self,"showlist not found at "+self.showlist_file)
            self.end('error',"showlist not found at "+self.showlist_file)

        # check profile and Pi Presents issues are compatible
        if self.showlist.profile_version() != self.pipresents_version():
            self.mon.err(self,"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")
            self.end('error',"Version of showlist " + self.showlist.profile_version_string + " is not  same as Pi Presents")


        # get the 'start' show from the showlist
        index = self.showlist.index_of_start_show()
        if index >=0:
            self.showlist.select(index)
            self.starter_show=self.showlist.selected_show()
        else:
            self.mon.err(self,"Show [start] not found in showlist")
            self.end('error',"Show [start] not found in showlist")


# ********************
# SET UP THE GUI
# ********************
        # turn off the screenblanking and saver
        if self.options['noblank'] is True:
            call(["xset","s", "off"])
            call(["xset","s", "-dpms"])

        self.root=Tk()   
       
        self.title='Pi Presents - '+ self.pp_profile
        self.icon_text= 'Pi Presents'
        self.root.title(self.title)
        self.root.iconname(self.icon_text)
        self.root.config(bg=self.starter_show['background-colour'])

        self.mon.log(self, 'monitor screen dimensions are ' + str(self.root.winfo_screenwidth()) + ' x ' + str(self.root.winfo_screenheight()) + ' pixels')
        if self.options['screensize'] =='':        
            self.screen_width = self.root.winfo_screenwidth()
            self.screen_height = self.root.winfo_screenheight()
        else:
            reason,message,self.screen_width,self.screen_height=self.parse_screen(self.options['screensize'])
            if reason =='error':
                self.mon.err(self,message)
                self.end('error',message)

        self.mon.log(self, 'forced screen dimensions (--screensize) are ' + str(self.screen_width) + ' x ' + str(self.screen_height) + ' pixels')
       
        # set window dimensions and decorations
        if self.options['fullscreen'] is False:
            self.window_width=int(self.root.winfo_screenwidth()*self.nonfull_window_width)
            self.window_height=int(self.root.winfo_screenheight()*self.nonfull_window_height)
            self.window_x=self.nonfull_window_x
            self.window_y=self.nonfull_window_y
            self.root.geometry("%dx%d%+d%+d" % (self.window_width,self.window_height,self.window_x,self.window_y))
        else:
            self.window_width=self.screen_width
            self.window_height=self.screen_height
            self.root.attributes('-fullscreen', True)
            os.system('unclutter &')
            self.window_x=0
            self.window_y=0  
            self.root.geometry("%dx%d%+d%+d"  % (self.window_width,self.window_height,self.window_x,self.window_y))
            self.root.attributes('-zoomed','1')

        # canvas cover the whole screen whatever the size of the window. 
        self.canvas_height=self.screen_height
        self.canvas_width=self.screen_width
  
        # make sure focus is set.
        self.root.focus_set()

        # define response to main window closing.
        self.root.protocol ("WM_DELETE_WINDOW", self.handle_user_abort)

        # setup a canvas onto which will be drawn the images or text
        self.canvas = Canvas(self.root, bg=self.starter_show['background-colour'])


        if self.options['fullscreen'] is True:
            self.canvas.config(height=self.canvas_height,
                               width=self.canvas_width,
                               highlightthickness=0)
        else:
            self.canvas.config(height=self.canvas_height,
                    width=self.canvas_width,
                        highlightthickness=1,
                               highlightcolor='yellow')
            
        self.canvas.place(x=0,y=0)
        # self.canvas.config(bg='black')
        self.canvas.focus_set()


                
# ****************************************
# INITIALISE THE TOUCHSCREEN DRIVER
# ****************************************

        # each driver takes a set of inputs, binds them to symboic names
        # and sets up a callback which returns the symbolic name when an input event occurs

        self.sr=ScreenDriver()
        # read the screen click area config file
        reason,message = self.sr.read(pp_dir,self.pp_home,self.pp_profile)
        if reason == 'error':
            self.end('error','cannot find, or error in screen.cfg')


        # create click areas on the canvas, must be polygon as outline rectangles are not filled as far as find_closest goes
        # click areas are made on the Pi Presents canvas not the show canvases.
        reason,message = self.sr.make_click_areas(self.canvas,self.handle_input_event)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


# ****************************************
# INITIALISE THE APPLICATION AND START
# ****************************************
        self.shutdown_required=False
        self.reboot_required=False
        self.terminate_required=False
        self.exitpipresents_required=False

        # initialise the I/O plugins by importing their drivers
        self.ioplugin_manager=IOPluginManager()
        reason,message=self.ioplugin_manager.init(self.pp_dir,self.pp_profile,self.root,self.handle_input_event)
        if reason == 'error':
            # self.mon.err(self,message)
            self.end('error',message)

        
        # kick off animation sequencer
        self.animate = Animate()
        self.animate.init(pp_dir,self.pp_home,self.pp_profile,self.canvas,200,self.handle_output_event)
        self.animate.poll()

        #create a showmanager ready for time of day scheduler and osc server
        show_id=-1
        self.show_manager=ShowManager(show_id,self.showlist,self.starter_show,self.root,self.canvas,self.pp_dir,self.pp_profile,self.pp_home)
        # first time through set callback to terminate Pi Presents if all shows have ended.
        self.show_manager.init(self.canvas,self.all_shows_ended_callback,self.handle_command,self.showlist)
        # Register all the shows in the showlist
        reason,message=self.show_manager.register_shows()
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)


        # Init OSCDriver, read config and start OSC server
        self.osc_enabled=False
        if self.network_connected is True:
            if os.path.exists(self.pp_profile + os.sep + 'pp_io_config'+ os.sep + 'osc.cfg'):
                self.oscdriver=OSCDriver()
                reason,message=self.oscdriver.init(self.pp_profile,
                                                   self.unit,self.interface,self.ip,
                                                   self.handle_command,self.handle_input_event,self.e_osc_handle_animate)
                if reason == 'error':
                    self.mon.err(self,message)
                    self.end('error',message)
                else:
                    self.osc_enabled=True
                    self.root.after(1000,self.oscdriver.start_server())

        
        # initialise ToD scheduler calculating schedule for today
        self.tod=TimeOfDay()
        reason,message,self.tod_enabled = self.tod.init(pp_dir,self.pp_home,self.pp_profile,self.showlist,self.root,self.handle_command)
        if reason == 'error':
            self.mon.err(self,message)
            self.end('error',message)
            
        # warn if the network not available when ToD required
        if self.tod_enabled is True and self.network_connected is False:
            self.mon.warn(self,'Network not connected  so Time of Day scheduler may be using the internal clock')

        # init the counter manager
        self.counter_manager=CounterManager()
        self.counter_manager.init()


        # warn about start shows and scheduler

        if self.starter_show['start-show']=='' and self.tod_enabled is False:
            self.mon.sched(self,None,"No Start Shows in Start Show and no shows scheduled") 
            self.mon.warn(self,"No Start Shows in Start Show and no shows scheduled")

        if self.starter_show['start-show'] !='' and self.tod_enabled is True:
            self.mon.sched(self,None,"Start Shows in Start Show and shows scheduled - conflict?") 
            self.mon.warn(self,"Start Shows in Start Show and shows scheduled - conflict?")

        # run the start shows
        self.run_start_shows()           

        # kick off the time of day scheduler which may run additional shows
        if self.tod_enabled is True:
            self.tod.poll()

        # start the I/O plugins input event generation
        self.ioplugin_manager.start()


        # start Tkinters event loop
        self.root.mainloop( )


    def parse_screen(self,size_text):
        fields=size_text.split('*')
        if len(fields)!=2:
            return 'error','do not understand --screensize comand option',0,0
        elif fields[0].isdigit()  is False or fields[1].isdigit()  is False:
            return 'error','dimensions are not positive integers in --screensize',0,0
        else:
            return 'normal','',int(fields[0]),int(fields[1])
        

# *********************
#  RUN START SHOWS
# ********************   
    def run_start_shows(self):
        self.mon.trace(self,'run start shows')
        # parse the start shows field and start the initial shows       
        show_refs=self.starter_show['start-show'].split()
        for show_ref in show_refs:
            reason,message=self.show_manager.control_a_show(show_ref,'open')
            if reason == 'error':
                self.mon.err(self,message)
                


# *********************
# User inputs
# ********************

    def e_osc_handle_animate(self,line):
        #jump  out of server thread
        self.root.after(1, lambda arg=line: self.osc_handle_animate(arg))

    def osc_handle_animate(self,line):
        self.mon.log(self,"animate command received: "+ line)
        #osc sends output events as a string
        reason,message,delay,name,param_type,param_values=self.animate.parse_animate_fields(line)
        if reason == 'error':
            self.mon.err(self,message)
            self.end(reason,message)
        self.handle_output_event(name,param_type,param_values,0)

    # output events are animate commands       
    def handle_output_event(self,symbol,param_type,param_values,req_time):
            reason,message=self.ioplugin_manager.handle_output_event(symbol,param_type,param_values,req_time)
            if reason =='error':
                self.mon.err(self,message)
                self.end(reason,message)



    # all input events call this callback providing a symbolic name.
    # handle events that affect PP overall, otherwise pass to all active shows
    def handle_input_event(self,symbol,source):
        self.mon.log(self,"event received: "+symbol + ' from '+ source)
        if symbol == 'pp-terminate':
            self.handle_user_abort()
            
        elif symbol == 'pp-shutdown':
            self.mon.err(self,'pp-shutdown removed in version 1.3.3a, see Release Notes')
            self.end('error','pp-shutdown removed in version 1.3.3a, see Release Notes')

            
        elif symbol == 'pp-shutdownnow':
            # need root.after to grt out of st thread
            self.root.after(1,self.shutdownnow_pressed)
            return
        
        elif symbol == 'pp-exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to grt out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            reason,message= self.show_manager.exit_all_shows()
        else:
            # pass the input event to all registered shows
            for show in self.show_manager.shows:
                show_obj=show[ShowManager.SHOW_OBJ]
                if show_obj is not None:
                    show_obj.handle_input_event(symbol)



    # commands are generaed by tracks and shows
    # they can open or close shows, generate input events and do special tasks
    # commands also generate osc outputs to other computers
    # handles one command provided as a line of text
    
    def handle_command(self,command_text,source='',show=''):
        # print 'PIPRESENTS ',command_text,'\n   Source',source,'from',show
        self.mon.log(self,"command received: " + command_text)
        if command_text.strip()=="":
            return

        fields= command_text.split()

        if fields[0] in ('osc','OSC'): 
            if self.osc_enabled is True:
                status,message=self.oscdriver.parse_osc_command(fields[1:])
                if status=='warn':
                    self.mon.warn(self,message)
                if status=='error':
                    self.mon.err(self,message)
                    self.end('error',message)
                return
        

        if fields[0] =='counter':
            status,message=self.counter_manager.parse_counter_command(fields[1:])
            if status=='error':
                self.mon.err(self,message)
                self.end('error',message)
            return

                           
        show_command=fields[0]
        if len(fields)>1:
            show_ref=fields[1]
        else:
            show_ref=''
        if show_command in ('open','close','closeall','openexclusive'):
            self.mon.sched(self, TimeOfDay.now,command_text + ' received from show:'+show)
            if self.shutdown_required is False and self.terminate_required is False:
                reason,message=self.show_manager.control_a_show(show_ref,show_command)
            else:
                return
            
        elif show_command =='monitor':
            self.handle_monitor_command(show_ref)
            return

        elif show_command =='cec':
            self.handle_cec_command(show_ref)
            return
        
        elif show_command == 'event':
            self.handle_input_event(show_ref,'Show Control')
            return
        
        elif show_command == 'exitpipresents':
            self.exitpipresents_required=True
            if self.show_manager.all_shows_exited() is True:
                # need root.after to get out of st thread
                self.root.after(1,self.e_all_shows_ended_callback)
                return
            else:
                reason,message= self.show_manager.exit_all_shows()

        elif show_command == 'shutdownnow':
            # need root.after to get out of st thread
            self.root.after(1,self.shutdownnow_pressed)
            return

        elif show_command == 'reboot':
            # need root.after to get out of st thread
            self.root.after(1,self.reboot_pressed)
            return
        
        else:
            reason='error'
            message = 'command not recognised: '+ show_command
            
        if reason=='error':
            self.mon.err(self,message)
        return


    def handle_monitor_command(self,command):
        if command == 'on':
            os.system('vcgencmd display_power 1 >/dev/null')
        elif command == 'off':
            os.system('vcgencmd display_power 0 >/dev/null')

    def handle_cec_command(self,command):
        if command == 'on':
            os.system('echo "on 0" | cec-client -s')
        elif command == 'standby':
            os.system('echo "standby 0" | cec-client -s')

        elif command == 'scan':
            os.system('echo scan | cec-client -s -d 1')
                      
    # deal with differnt commands/input events

    def shutdownnow_pressed(self):
        self.shutdown_required=True
        if self.show_manager.all_shows_exited() is True:
           self.all_shows_ended_callback('normal','no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows()

    def reboot_pressed(self):
        self.reboot_required=True
        if self.show_manager.all_shows_exited() is True:
           self.all_shows_ended_callback('normal','no shows running')
        else:
            # calls exit method of all shows, results in all_shows_closed_callback
            self.show_manager.exit_all_shows() 


    def handle_sigterm(self,signum,fframe):
        self.mon.log(self,'SIGTERM received - '+ str(signum))
        self.terminate()


    def handle_user_abort(self):
        self.mon.log(self,'User abort received')
        self.terminate()

    def terminate(self):
        self.mon.log(self, "terminate received")
        self.terminate_required=True
        needs_termination=False
        for show in self.show_manager.shows:
            # print  show[ShowManager.SHOW_OBJ], show[ShowManager.SHOW_REF]
            if show[ShowManager.SHOW_OBJ] is not None:
                needs_termination=True
                self.mon.log(self,"Sent terminate to show "+ show[ShowManager.SHOW_REF])
                # call shows terminate method
                # eventually the show will exit and after all shows have exited all_shows_callback will be executed.
                show[ShowManager.SHOW_OBJ].terminate()
        if needs_termination is False:
            self.end('killed','killed - no termination of shows required')



# ******************************
# Ending Pi Presents after all the showers and players are closed
# **************************

    def e_all_shows_ended_callback(self):
        self.all_shows_ended_callback('normal','no shows running')

    # callback from ShowManager when all shows have ended
    def all_shows_ended_callback(self,reason,message):
        self.canvas.config(bg=self.starter_show['background-colour'])
        if reason in ('killed','error') or self.shutdown_required is True or self.exitpipresents_required is True or self.reboot_required is True:
            self.end(reason,message)

    def end(self,reason,message):
        self.mon.log(self,"Pi Presents ending with reason: " + reason)
        if self.root is not None:
            self.root.destroy()
        self.tidy_up()
        if reason == 'killed':
            if self.email_enabled is True and self.mailer.email_on_terminate is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Terminated'
                message = time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message)
            self.mon.sched(self, None,"Pi Presents Terminated, au revoir\n")
            self.mon.log(self, "Pi Presents Terminated, au revoir")
                          
            # close logging files
            self.mon.finish()
            print 'Uncollectable Garbage',gc.collect()
            # objgraph.show_backrefs(objgraph.by_type('Monitor'))
            sys.exit(101)
                          
        elif reason == 'error':
            if self.email_enabled is True and self.mailer.email_on_error is True:
                subject= '[Pi Presents] ' + self.unit + ': PP Exited with reason: Error'
                message_text = 'Error message: '+ message + '\n'+ time.strftime("%Y-%m-%d %H:%M") + '\n ' + self.unit + '\n ' + self.interface + '\n ' + self.ip 
                self.send_email(reason,subject,message_text)   
            self.mon.sched(self,None, "Pi Presents closing because of error, sorry\n")
            self.mon.log(self, "Pi Presents closing because of error, sorry")
                          
            # close logging files 
            self.mon.finish()
            print 'uncollectable garbage',gc.collect()
            sys.exit(102)

        else:           
            self.mon.sched(self,None,"Pi Presents  exiting normally, bye\n")
            self.mon.log(self,"Pi Presents  exiting normally, bye")
            
            # close logging files 
            self.mon.finish()
            if self.reboot_required is True:
                # print 'REBOOT'
                call (['sudo','reboot'])
            if self.shutdown_required is True:
                # print 'SHUTDOWN'
                call (['sudo','shutdown','now','SHUTTING DOWN'])
            print 'uncollectable garbage',gc.collect()
            sys.exit(100)


    # tidy up all the peripheral bits of Pi Presents
    def tidy_up(self):
        self.handle_monitor_command('on')
        self.mon.log(self, "Tidying Up")
        # turn screen blanking back on
        if self.options['noblank'] is True:
            call(["xset","s", "on"])
            call(["xset","s", "+dpms"])
            
        # tidy up animation
        if self.animate is not None:
            self.animate.terminate()

        # tidy up i/o plugins
        if self.ioplugin_manager != None:
            self.ioplugin_manager.terminate()

        if self.osc_enabled is True:
            self.oscdriver.terminate()
            
        # tidy up time of day scheduler
        if self.tod_enabled is True:
            self.tod.terminate()



# *******************************
# Connecting to network and email
# *******************************

    def init_network(self):

        timeout=int(self.options['nonetwork'])
        if timeout== 0:
            self.network_connected=False
            self.unit=''
            self.ip=''
            self.interface=''
            return
        
        self.network=Network()
        self.network_connected=False

        # try to connect to network
        self.mon.log (self, 'Waiting up to '+ str(timeout) + ' seconds for network')
        success=self.network.wait_for_network(timeout)
        if success is False:
            self.mon.warn(self,'Failed to connect to network after ' + str(timeout) + ' seconds')
            # tkMessageBox.showwarning("Pi Presents","Failed to connect to network so using fake-hwclock")
            return

        self.network_connected=True
        self.mon.sched (self, None,'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))
        self.mon.log (self, 'Time after network check is '+ time.strftime("%Y-%m-%d %H:%M.%S"))

        # Get web configuration
        self.network_details=False
        network_options_file_path=self.pp_dir+os.sep+'pp_config'+os.sep+'pp_web.cfg'
        if not os.path.exists(network_options_file_path):
            self.mon.warn(self,"pp_web.cfg not found at "+network_options_file_path)
            return
        self.mon.log(self, 'Found pp_web.cfg in ' + network_options_file_path)

        self.network.read_config(network_options_file_path)
        self.unit=self.network.unit

        # get interface and IP details of preferred interface
        self.interface,self.ip = self.network.get_preferred_ip()
        if self.interface == '':
            self.network_connected=False
            return
        self.network_details=True
        self.mon.log (self, 'Network details ' + self.unit + ' ' + self.interface + ' ' +self.ip)


    def init_mailer(self):

        self.email_enabled=False
        email_file_path = self.pp_dir+os.sep+'pp_config'+os.sep+'pp_email.cfg'
        if not os.path.exists(email_file_path):
            self.mon.log(self,'pp_email.cfg not found at ' + email_file_path)
            return
        self.mon.log(self,'Found pp_email.cfg at ' + email_file_path)
        self.mailer=Mailer()
        self.mailer.read_config(email_file_path)
        # all Ok so can enable email if config file allows it.
        if self.mailer.email_allowed is True:
            self.email_enabled=True
            self.mon.log (self,'Email Enabled')


    def try_connect(self):
        tries=1
        while True:
            success, error = self.mailer.connect()
            if success is True:
                return True
            else:
                self.mon.log(self,'Failed to connect to email SMTP server ' + str(tries) +  '\n ' +str(error))
                tries +=1
                if tries >5:
                    self.mon.log(self,'Failed to connect to email SMTP server after ' + str(tries))
                    return False


    def send_email(self,reason,subject,message):
        if self.try_connect() is False:
            return False
        else:
            success,error = self.mailer.send(subject,message)
            if success is False:
                self.mon.log(self, 'Failed to send email: ' + str(error))
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect after send:' + str(error))
                return False
            else:
                self.mon.log(self,'Sent email for ' + reason)
                success,error=self.mailer.disconnect()
                if success is False:
                    self.mon.log(self,'Failed disconnect from email server ' + str(error))
                return True