def __init__(self,show_id,root,canvas,show_params,track_params,pp_dir,pp_home,pp_profile): self.mon=Monitor() self.mon.on() self.root=root self.canvas=canvas self.show_id=show_id self.track_params=track_params self.show_params=show_params self.pp_dir=pp_dir self.pp_home=pp_home self.pp_profile=pp_profile # 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.show_params['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'] self.centre_x = int(self.canvas['width'])/2 self.centre_y = int(self.canvas['height'])/2 # keep tick as an integer sub-multiple of 1 second self.tick = 100 # tick time for image display (milliseconds) self.dwell = 1000*self.duration #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()
def __init__(self, track, pp_dir, pp_home, show_params, ct): self.track = track self.show_params = show_params self.pp_home = pp_home self.ct = ct self.break_from_loop = False # create and instance of a Tkinter top level window and refer to it as 'my_window' my_window = Tk() my_window.title("AudioPlayer Test Harness") # change the look of the window my_window.configure(background='grey') window_width = 1500 window_height = 900 canvas_height = window_height canvas_width = window_width #defne response to main window closing my_window.protocol("WM_DELETE_WINDOW", self.terminate) my_window.geometry("%dx%d+200+20" % (window_width, window_height)) # Always use CTRL-Break key to close the program as a get out of jail my_window.bind("<Break>", self.e_terminate) my_window.bind("s", self.play_event) my_window.bind("p", self.pause_event) my_window.bind("q", self.stop_event) my_window.bind("l", self.loop_event) my_window.bind("n", self.next_event) #setup a canvas onto which will not be drawn the video!! canvas = Canvas(my_window, bg='black') canvas.config(height=canvas_height, width=canvas_width) canvas.pack() # make sure focus is set on canvas. canvas.focus_set() self.canvas = canvas #create an instance of PPIO and start polling self.ppio = PPIO() self.ppio.init(pp_dir, pp_home, self.canvas, 50, self.callback) self.ppio.poll() my_window.mainloop()
def __init__(self,track,pp_dir,pp_home,show_params,ct): self.track=track self.show_params=show_params self.pp_home=pp_home self.ct = ct self.break_from_loop=False # create and instance of a Tkinter top level window and refer to it as 'my_window' my_window=Tk() my_window.title("AudioPlayer Test Harness") # change the look of the window my_window.configure(background='grey') window_width=1500 window_height=900 canvas_height=window_height canvas_width=window_width #defne response to main window closing my_window.protocol ("WM_DELETE_WINDOW", self.terminate) my_window.geometry("%dx%d+200+20" %(window_width,window_height)) # Always use CTRL-Break key to close the program as a get out of jail my_window.bind("<Break>",self.e_terminate) my_window.bind("s", self.play_event) my_window.bind("p", self.pause_event) my_window.bind("q", self.stop_event) my_window.bind("l", self.loop_event) my_window.bind("n", self.next_event) #setup a canvas onto which will not be drawn the video!! canvas = Canvas(my_window, bg='black') canvas.config(height=canvas_height, width=canvas_width) canvas.pack() # make sure focus is set on canvas. canvas.focus_set() self.canvas=canvas #create an instance of PPIO and start polling self.ppio= PPIO() self.ppio.init(pp_dir,pp_home,self.canvas,50,self.callback) self.ppio.poll() my_window.mainloop()
def __init__(self, show_id, canvas, pp_home, show_params, track_params ): """ canvas - the canvas onto which the video is to be drawn (not!!) cd - configuration dictionary track_params - config dictionary for this track overides cd """ self.mon=Monitor() self.mon.on() #instantiate arguments self.show_id=show_id self.show_params=show_params #configuration dictionary for the videoplayer self.pp_home=pp_home self.canvas = canvas #canvas onto which video should be played but isn't! Use as widget for alarm self.track_params=track_params # 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) + ' ' self.omx_volume=' ' #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.omx=OMXDriver(self.canvas) self._tick_timer=None self.error=False self.terminate_required=False self._init_play_state_machine()
def __init__(self,show_id,canvas,pp_home,show_params,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.show_id=show_id self.canvas=canvas self.pp_home=pp_home self.show_params=show_params self.track_params=track_params # open resources self.rr=ResourceReader() # get config from medialist if there. self.animate_begin_text=self.track_params['animate-begin'] self.animate_end_text=self.track_params['animate-end'] if 'duration' in self.track_params and self.track_params['duration']<>"": self.duration= int(self.track_params['duration']) else: self.duration= int(self.show_params['duration']) if 'transition' in self.track_params and self.track_params['transition']<>"": self.transition= self.track_params['transition'] else: self.transition= self.show_params['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 #create an instance of PPIO so we can create gpio events self.ppio = PPIO()
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 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
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 #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']
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()
class VideoPlayer: _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, canvas, pp_home, show_params, track_params ): """ canvas - the canvas onto which the video is to be drawn (not!!) cd - configuration dictionary track_params - config dictionary for this track overides cd """ self.mon=Monitor() self.mon.on() #instantiate arguments self.show_id=show_id self.show_params=show_params #configuration dictionary for the videoplayer self.pp_home=pp_home self.canvas = canvas #canvas onto which video should be played but isn't! Use as widget for alarm self.track_params=track_params # 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) + ' ' self.omx_volume=' ' #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.omx=OMXDriver(self.canvas) self._tick_timer=None self.error=False self.terminate_required=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): #instantiate arguments self.ready_callback=ready_callback #callback when ready to play 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 videoplayer # 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() # and start playing the video. if self.play_state == VideoPlayer._CLOSED: self.mon.log(self,">play track received") self._start_play_state_machine(track) return True else: self.mon.log(self,"!< play track rejected") return False def key_pressed(self,key_name): 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): if button =='pause': self._pause() return elif button=='stop': self._stop() return def terminate(self,reason): # circumvents state machine self.terminate_required=True if self.omx<>None: self.mon.log(self,"sent terminate to omxdriver") self.omx.terminate(reason) else: self.mon.log(self,"terminate, omxdriver not running") self._end() # *************************************** # 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._stop_required_signal=True #respond to internal error def _error(self): self.error=True self._stop_required_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, used? def _control(self,char): if self.play_state==VideoPlayer._PLAYING: self.mon.log(self,"> send control ot omx: "+ char) self.omx.control(char) return True else: self.mon.log(self,"!<control rejected") return False # called to end omxdriver def _end(self): os.system("xrefresh -display :0") if self._tick_timer<>None: self.canvas.after_cancel(self._tick_timer) self._tick_timer=None if self.error==True: self.end_callback("error",'error') self=None elif self.terminate_required==True: self.end_callback("killed",'killed') 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') 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 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._stop_required_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._stop_required_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.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 # callback to the calling object to e.g remove egg timer. if self.ready_callback<>None: self.ready_callback() self.canvas.config(bg='black') self.canvas.delete(ALL) self.display_image() self.play_state=VideoPlayer._PLAYING self.mon.log(self," State machine: omx_playing started") self._do_starting() 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._stop_required_signal==True: self.mon.log(self," Service stop required signal") self._stop_omx() self._stop_required_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)) self.play_state = VideoPlayer._ENDING self._do_playing() 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) self._do_ending() # 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() else: self._tick_timer=self.canvas.after(200, self._play_state_machine) # allow calling object do things in each state by calling the appropriate callback def _do_playing(self): self.video_position=self.omx.video_position self.audio_position=self.omx.audio_position if self.playing_callback<>None: self.playing_callback() def _do_starting(self): self.video_position=0.0 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_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 # ***************** # image and text # ***************** def display_image(self): # 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 text ") self.canvas.update_idletasks( )
class MessagePlayer: """ Displays lines of text in the centre of a coloured screen with background image See pp_imageplayer for common software design description """ # ******************* # 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() self.root=root self.canvas=canvas self.show_id=show_id self.track_params=track_params self.show_params=show_params self.pp_dir=pp_dir self.pp_home=pp_home self.pp_profile=pp_profile # 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.show_params['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'] self.centre_x = int(self.canvas['width'])/2 self.centre_y = int(self.canvas['height'])/2 # keep tick as an integer sub-multiple of 1 second self.tick = 100 # tick time for image display (milliseconds) self.dwell = 1000*self.duration #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() def play(self, text, showlist, end_callback, ready_callback, enable_menu=False): # instantiate arguments self.text=text self.showlist=showlist self.end_callback=end_callback self.ready_callback=ready_callback self.enable_menu=enable_menu #init state and signals self.quit_signal=False self.tick_timer=None self.drawn=None # 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.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 text display self.start_dwell() def terminate(self,reason): # no lower level things to terminate so just go to end self.end(reason,'kill or error') def get_links(self): return self.track_params['links'] def input_pressed(self,symbol): self.mon.log(self,"input received: "+symbol) if symbol=='stop': self.stop() # ******************* # internal functions # ******************* def stop(self): self.quit_signal=True # ******************* # sequencing # ******************* def start_dwell(self): self.dwell_counter=0 if self.ready_callback<>None: self.ready_callback() 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.dwell<>0: self.dwell_counter=self.dwell_counter+1 if self.dwell_counter==self.dwell/self.tick: self.end('normal','finished') else: self.tick_timer=self.canvas.after(self.tick, self.do_dwell) else: self.tick_timer=self.canvas.after(self.tick, self.do_dwell) # ***************** # 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 # 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_colour<>'': self.canvas.config(bg=self.background_colour) self.canvas.delete('pp-content') 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,"Message background file not found: "+ self.background_img_file) self.end('error',"Message 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') # 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') # execute the plugin if required if self.track_params['plugin']<>'': reason,message,self.text = self.pim.do_plugin(self.text,self.track_params['plugin']) if reason <> 'normal': return reason,message # display message text if self.track_params['message-x']<>'': self.canvas.create_text(int(self.track_params['message-x']), int(self.track_params['message-y']), text=self.text.rstrip('\n'), fill=self.track_params['message-colour'], font=self.track_params['message-font'], justify=self.track_params['message-justify'], anchor = 'nw', tag='pp-content') else: self.canvas.create_text(int(self.canvas['width'])/2, int(self.canvas['height'])/2, text=self.text.rstrip('\n'), fill=self.track_params['message-colour'], font=self.track_params['message-font'], justify=self.track_params['message-justify'], tag='pp-content') # display instructions (hint) 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
def __init__(self,show_id,root,canvas,show_params,track_params,pp_dir,pp_home,pp_profile): """ show_id - show instance that player is run from (for monitoring only) canvas - the canvas onto which the image is to be drawn show_params - dictionary of show parameters track_params - disctionary of track paramters pp_home - data home directory pp_profile - profile name """ self.mon=Monitor() self.mon.off() 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 # open resources self.rr=ResourceReader() # get parameters self.animate_begin_text=self.track_params['animate-begin'] self.animate_end_text=self.track_params['animate-end'] if self.track_params['duration']<>"": self.duration= int(self.track_params['duration']) else: self.duration= int(self.show_params['duration']) #create an instance of PPIO so we can create gpio events self.ppio = PPIO() # 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 image window from profile if self.track_params['image-window'].strip()<>"": self.image_window= self.track_params['image-window'].strip() else: self.image_window= self.show_params['image-window'].strip() # 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)
class ImagePlayer: """ Displays an image on a canvas for a period of time. Image display can be paused and interrupted __init_ just makes sure that all the things the player needs are available play starts playing the track and returns immeadiately play must end up with a call to tkinter's after, the after callback will interrogae the playing state at intervals and eventually return through end_callback input-pressed receives user input while the track is playing. it might pass the input on to the driver Input-pressed must not wait, it must set a signal and return immeadiately. The signal is interrogated by the after callback. """ # slide state constants NO_SLIDE = 0 SLIDE_DWELL= 1 # ******************* # external commands # ******************* def __init__(self,show_id,root,canvas,show_params,track_params,pp_dir,pp_home,pp_profile): """ show_id - show instance that player is run from (for monitoring only) canvas - the canvas onto which the image is to be drawn show_params - dictionary of show parameters track_params - disctionary of track paramters pp_home - data home directory pp_profile - profile name """ self.mon=Monitor() self.mon.off() 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 # open resources self.rr=ResourceReader() # get parameters self.animate_begin_text=self.track_params['animate-begin'] self.animate_end_text=self.track_params['animate-end'] if self.track_params['duration']<>"": self.duration= int(self.track_params['duration']) else: self.duration= int(self.show_params['duration']) #create an instance of PPIO so we can create gpio events self.ppio = PPIO() # 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 image window from profile if self.track_params['image-window'].strip()<>"": self.image_window= self.track_params['image-window'].strip() else: self.image_window= self.show_params['image-window'].strip() # 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) def play(self, track, showlist, end_callback, ready_callback, enable_menu=False): """ track - filename of track to be played showlist - from which track was taken end_callback - callback when player terminates ready_callback - callback just before anytthing is displayed enable_menu - there will be a child track so display the hint text """ # instantiate arguments self.track=track self.showlist=showlist self.enable_menu=enable_menu self.ready_callback=ready_callback self.end_callback=end_callback #init state and signals self.canvas_centre_x = int(self.canvas['width'])/2 self.canvas_centre_y = int(self.canvas['height'])/2 self.tick = 100 # tick time for image display (milliseconds) self.dwell = 10*self.duration self.dwell_counter=0 self.state=ImagePlayer.NO_SLIDE self.quit_signal=False self.drawn=None self.paused=False self.pause_text=None self.tick_timer=None #parse the image_window error,self.command,self.has_coords,self.image_x1,self.image_y1,self.image_x2,self.image_y2,self.filter=self.parse_window(self.image_window) if error =='error': self.mon.err(self,'image window error: '+self.image_window) self.end('error','image window error') else: # 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.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 dwelling self.mon.log(self,'playing track from show Id: '+str(self.show_id)) self.start_dwell() def input_pressed(self,symbol): if symbol =='pause': self.pause() elif symbol=='stop': self.stop() return def terminate(self,reason): # no lower level things to terminate so just go to end self.end(reason,'kill or error') def get_links(self): return self.track_params['links'] # ******************* # internal functions # ******************* def pause(self): if not self.paused: self.paused = True else: self.paused=False #print "self.paused is "+str(self.paused) def stop(self): self.quit_signal=True # ****************************************** # Sequencing # ******************************************** def start_dwell(self): if self.ready_callback<>None: self.ready_callback() self.state=ImagePlayer.SLIDE_DWELL 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 #print "self.paused is "+str(self.paused) #if self.pause_text<>None: #print "self.pause_text exists" # one time flipping of pause text #NIK if self.paused==True and self.pause_text==None: self.pause_text=self.canvas.create_text(0,900, anchor=NW, text=self.resource('imageplayer','m01'), fill="white", font="arial 25 bold") self.canvas.update_idletasks( ) #NIK 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<>0 and self.dwell_counter==self.dwell: self.end('normal','user quit or duration exceeded') else: self.tick_timer=self.canvas.after(self.tick, self.do_dwell) # ***************** # ending the player # ***************** def end(self,reason,message): self.state=self.NO_SLIDE # 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") #NIK if self.pause_text<>None: self.canvas.delete(self.pause_text) self.pause_text=None self.canvas.update_idletasks( ) #NIK self=None # ********************************** # displaying things # ********************************** def display_content(self): #background colour if self.background_colour<>'': self.canvas.config(bg=self.background_colour) 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,"Background file not found: "+ self.background_img_file) self.end('error',"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 #get the track to be displayed if os.path.exists(self.track)==True: self.pil_image=PIL.Image.open(self.track) else: self.pil_image=None # display track image if self.pil_image<>None: self.image_width,self.image_height=self.pil_image.size if self.command=='original': # display image at its original size if self.has_coords==False: # load and display the unmodified image in centre self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image) self.drawn = self.canvas.create_image(self.canvas_centre_x, self.canvas_centre_y, image=self.tk_img, anchor=CENTER, tag='pp-content') else: # load and display the unmodified image at x1,y1 self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image) self.drawn = self.canvas.create_image(self.image_x1, self.image_y1, image=self.tk_img, anchor=NW, tag='pp-content') elif self.command in ('fit','shrink'): # shrink fit the window or screen preserving aspect if self.has_coords==True: window_width=self.image_x2 - self.image_x1 window_height=self.image_y2 - self.image_y1 window_centre_x=(self.image_x2+self.image_x1)/2 window_centre_y= (self.image_y2+self.image_y1)/2 else: window_width=int(self.canvas['width']) window_height=int(self.canvas['height']) window_centre_x=self.canvas_centre_x window_centre_y=self.canvas_centre_y if (self.image_width > window_width or self.image_height > window_height and self.command=='fit') or (self.command=='shrink') : # original image is larger or , shrink it to fit the screen preserving aspect self.pil_image.thumbnail((window_width,window_height),eval(self.filter)) self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image) self.drawn = self.canvas.create_image(window_centre_x, window_centre_y, image=self.tk_img, anchor=CENTER, tag='pp-content') else: # fitting and original image is smaller, expand it to fit the screen preserving aspect prop_x = float(window_width) / self.image_width prop_y = float(window_height) / self.image_height if prop_x > prop_y: prop=prop_y else: prop=prop_x increased_width=int(self.image_width * prop) increased_height=int(self.image_height * prop) # print 'result',prop, increased_width,increased_height self.pil_image=self.pil_image.resize((increased_width, increased_height),eval(self.filter)) self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image) self.drawn = self.canvas.create_image(window_centre_x, window_centre_y, image=self.tk_img, anchor=CENTER, tag='pp-content') elif self.command in ('warp'): # resize to window or screen without preserving aspect if self.has_coords==True: window_width=self.image_x2 - self.image_x1 window_height=self.image_y2 - self.image_y1 window_centre_x=(self.image_x2+self.image_x1)/2 window_centre_y= (self.image_y2+self.image_y1)/2 else: window_width=int(self.canvas['width']) window_height=int(self.canvas['height']) window_centre_x=self.canvas_centre_x window_centre_y=self.canvas_centre_y self.pil_image=self.pil_image.resize((window_width, window_height),eval(self.filter)) self.tk_img=PIL.ImageTk.PhotoImage(self.pil_image) self.drawn = self.canvas.create_image(window_centre_x, window_centre_y, image=self.tk_img, anchor=CENTER, tag='pp-content') # display hint 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.canvas.tag_raise('pp-click-area') self.canvas.update_idletasks( ) return 'normal','' # ********************************** # utilties # ********************************** def parse_window(self,line): fields = line.split() # check there is a command field if len(fields) < 1: return 'error','',False,0,0,0,0,'' # deal with original whch has 0 or 2 arguments filter='' if fields[0]=='original': if len(fields) not in (1,3): return 'error','',False,0,0,0,0,'' # deal with window coordinates if len(fields) == 3: #window is specified if not (fields[1].isdigit() and fields[2].isdigit()): return 'error','',False,0,0,0,0,'' has_window=True return 'normal',fields[0],has_window,int(fields[1]),int(fields[2]),0,0,filter else: # no window has_window=False return 'normal',fields[0],has_window,0,0,0,0,filter #deal with remainder which has 1, 2, 5 or 6arguments # check basic syntax if fields[0] not in ('shrink','fit','warp'): return 'error','',False,0,0,0,0,'' if len(fields) not in (1,2,5,6): return 'error','',False,0,0,0,0,'' if len(fields)==6 and fields[5] not in ('NEAREST','BILINEAR','BICUBIC','ANTIALIAS'): return 'error','',False,0,0,0,0,'' if len(fields)==2 and fields[1] not in ('NEAREST','BILINEAR','BICUBIC','ANTIALIAS'): return 'error','',False,0,0,0,0,'' # deal with window coordinates if len(fields) in (5,6): #window is specified if not (fields[1].isdigit() and fields[2].isdigit() and fields[3].isdigit() and fields[4].isdigit()): return 'error','',False,0,0,0,0,'' has_window=True if len(fields)==6: filter=fields[5] else: filter='PIL.Image.NEAREST' return 'normal',fields[0],has_window,int(fields[1]),int(fields[2]),int(fields[3]),int(fields[4]),filter else: # no window has_window=False if len(fields)==2: filter=fields[1] else: filter='PIL.Image.NEAREST' return 'normal',fields[0],has_window,0,0,0,0,filter 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) if value==False: self.mon.err(self, "resource: "+section +': '+ item + " not found" ) self.error() else: return value
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()
class Test: def __init__(self, track, pp_dir, pp_home, show_params, ct): self.track = track self.show_params = show_params self.pp_home = pp_home self.ct = ct self.break_from_loop = False # create and instance of a Tkinter top level window and refer to it as 'my_window' my_window = Tk() my_window.title("AudioPlayer Test Harness") # change the look of the window my_window.configure(background='grey') window_width = 1500 window_height = 900 canvas_height = window_height canvas_width = window_width #defne response to main window closing my_window.protocol("WM_DELETE_WINDOW", self.terminate) my_window.geometry("%dx%d+200+20" % (window_width, window_height)) # Always use CTRL-Break key to close the program as a get out of jail my_window.bind("<Break>", self.e_terminate) my_window.bind("s", self.play_event) my_window.bind("p", self.pause_event) my_window.bind("q", self.stop_event) my_window.bind("l", self.loop_event) my_window.bind("n", self.next_event) #setup a canvas onto which will not be drawn the video!! canvas = Canvas(my_window, bg='black') canvas.config(height=canvas_height, width=canvas_width) canvas.pack() # make sure focus is set on canvas. canvas.focus_set() self.canvas = canvas #create an instance of PPIO and start polling self.ppio = PPIO() self.ppio.init(pp_dir, pp_home, self.canvas, 50, self.callback) self.ppio.poll() my_window.mainloop() def callback(self, index, name, edge): print name, edge #key presses def e_terminate(self, event): self.terminate() def play_event(self, event): self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params, self.ct) self.vp.play(self.track, self.on_end, self.do_ready, False, self.do_starting, self.do_playing, self.do_finishing) # toggles pause def pause_event(self, event): self.vp.key_pressed('p') def stop_event(self, event): self.break_from_loop = True self.vp.key_pressed('escape') def loop_event(self, event): #just kick off the first track, callback decides what to do next self.break_from_loop = False self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params) self.vp.play(self.track, self.what_next, self, do_ready, False, self.do_starting, self.do_playing, self.do_finishing) def next_event(self, event): self.break_from_loop = False self.vp.key_pressed('down') def what_next(self, reason, message): self.vp = None if reason in ('killed', 'error'): self.end(reason, message) else: if self.break_from_loop == True: self.break_from_loop = False print "test harness: loop interupted" return else: self.vp = AudioPlayer(self.canvas, self.show_params) self.vp.play(self.track, self.what_next, self.do_starting, self.do_playing, self.do_finishing) def on_end(self, reason, message): self.vp = None print "Test Class: callback from AudioPlayer says: " + message if reason in ('killed', 'error'): self.end(reason, message) else: return def do_ready(self): print "test class message from AudioPlayer: ready to play" return def do_starting(self): print "test class message from AudioPlayer: do starting" return def do_playing(self): #self.display_time.set(self.time_string(self.vp.audio_position)) # print "test class message from AudioPlayer: do playing" return def do_finishing(self): print "test class message from AudioPlayer: do ending" return def terminate(self): if self.vp == None: self.end('killled', 'killled') else: self.vp.terminate('killed') return def _end(self, reason, message): self.ppio.terminate() 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
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
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()
class Test: def __init__(self, track, pp_dir, pp_home, show_params, ct): self.track = track self.show_params = show_params self.pp_home = pp_home self.ct = ct self.break_from_loop = False # create and instance of a Tkinter top level window and refer to it as 'my_window' my_window = Tk() my_window.title("AudioPlayer Test Harness") # change the look of the window my_window.configure(background="grey") window_width = 1500 window_height = 900 canvas_height = window_height canvas_width = window_width # defne response to main window closing my_window.protocol("WM_DELETE_WINDOW", self.terminate) my_window.geometry("%dx%d+200+20" % (window_width, window_height)) # Always use CTRL-Break key to close the program as a get out of jail my_window.bind("<Break>", self.e_terminate) my_window.bind("s", self.play_event) my_window.bind("p", self.pause_event) my_window.bind("q", self.stop_event) my_window.bind("l", self.loop_event) my_window.bind("n", self.next_event) # setup a canvas onto which will not be drawn the video!! canvas = Canvas(my_window, bg="black") canvas.config(height=canvas_height, width=canvas_width) canvas.pack() # make sure focus is set on canvas. canvas.focus_set() self.canvas = canvas # create an instance of PPIO and start polling self.ppio = PPIO() self.ppio.init(pp_dir, pp_home, self.canvas, 50, self.callback) self.ppio.poll() my_window.mainloop() def callback(self, index, name, edge): print name, edge # key presses def e_terminate(self, event): self.terminate() def play_event(self, event): self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params, self.ct) self.vp.play( self.track, self.on_end, self.do_ready, False, self.do_starting, self.do_playing, self.do_finishing ) # toggles pause def pause_event(self, event): self.vp.key_pressed("p") def stop_event(self, event): self.break_from_loop = True self.vp.key_pressed("escape") def loop_event(self, event): # just kick off the first track, callback decides what to do next self.break_from_loop = False self.vp = AudioPlayer(self.canvas, self.pp_home, self.show_params) self.vp.play( self.track, self.what_next, self, do_ready, False, self.do_starting, self.do_playing, self.do_finishing ) def next_event(self, event): self.break_from_loop = False self.vp.key_pressed("down") def what_next(self, reason, message): self.vp = None if reason in ("killed", "error"): self.end(reason, message) else: if self.break_from_loop == True: self.break_from_loop = False print "test harness: loop interupted" return else: self.vp = AudioPlayer(self.canvas, self.show_params) self.vp.play(self.track, self.what_next, self.do_starting, self.do_playing, self.do_finishing) def on_end(self, reason, message): self.vp = None print "Test Class: callback from AudioPlayer says: " + message if reason in ("killed", "error"): self.end(reason, message) else: return def do_ready(self): print "test class message from AudioPlayer: ready to play" return def do_starting(self): print "test class message from AudioPlayer: do starting" return def do_playing(self): # self.display_time.set(self.time_string(self.vp.audio_position)) # print "test class message from AudioPlayer: do playing" return def do_finishing(self): print "test class message from AudioPlayer: do ending" return def terminate(self): if self.vp == None: self.end("killled", "killled") else: self.vp.terminate("killed") return def _end(self, reason, message): self.ppio.terminate() exit()
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()
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()
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()
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( )
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( )
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()
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 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,show_id,canvas,pp_home,show_params,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.show_id=show_id self.canvas=canvas self.pp_home=pp_home self.show_params=show_params self.track_params=track_params # open resources self.rr=ResourceReader() # get config from medialist if there. self.animate_begin_text=self.track_params['animate-begin'] self.animate_end_text=self.track_params['animate-end'] if 'duration' in self.track_params and self.track_params['duration']<>"": self.duration= int(self.track_params['duration']) else: self.duration= int(self.show_params['duration']) if 'transition' in self.track_params and self.track_params['transition']<>"": self.transition= self.track_params['transition'] else: self.transition= self.show_params['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 #create an instance of PPIO so we can create gpio events self.ppio = PPIO() 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 # and start image rendering self.mon.log(self,'playing track from show Id: '+str(self.show_id)) 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 or reason=='error': self.end_callback("error",message) self=None elif self.kill_required_signal==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') 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.pil_image<>None or self.enable_menu== True or self.show_params['show-text']<> '' or self.track_params['track-text']<> '': self.canvas.delete(ALL) 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.canvas.update_idletasks() # 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() 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.canvas.config(bg='black') 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 instructions if enabled if self.enable_menu== True: self.canvas.create_text(self.centre_x, 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.canvas.update_idletasks( )