From 0883d19eeee9e48ed3d4e095dd076da789d083d6 Mon Sep 17 00:00:00 2001 From: langolierz Date: Sun, 24 Dec 2017 03:59:29 +0000 Subject: [PATCH] updated to py3, replacing omxdriver --- data_centre.py | 16 +- display_centre.py | 25 +- display_data.json | 2 +- next_bank_number.json | 2 +- old-omxdriver.py | 547 ++++++++++++++++++++++++++++++++++++++++ omxdriver.py | 575 ++---------------------------------------- video_centre.py | 16 +- 7 files changed, 610 insertions(+), 573 deletions(-) create mode 100644 old-omxdriver.py diff --git a/data_centre.py b/data_centre.py index 9c439de..d949bef 100644 --- a/data_centre.py +++ b/data_centre.py @@ -5,14 +5,14 @@ import os from random import randint import time import inspect -from ffprobe import FFProbe +#from ffprobe import FFProbe current_message = None def set_message(message): global current_message - print 'trying to set message' + print('trying to set message') current_message = message def clear_message(): @@ -70,7 +70,7 @@ PATH_TO_DATA_OBJECTS = get_the_current_dir_path() PATH_TO_BROWSER = get_path_to_browser() EMPTY_BANK = dict(name='',location='',length=-1,start=-1,end=-1) DEV_MODE = read_json(SETTINGS_JSON)[6]["value"] -print DEV_MODE +print(DEV_MODE) ####<<<< data methods for browser tab >>>>##### class data(object): ######## a data class used mainly for managing the browser list ######## @@ -179,15 +179,15 @@ def create_new_bank_mapping(bank_number,file_name,memory_bank=[]): update_a_banks_data(bank_number, new_bank, memory_bank) def get_length_for_file(location): - video_length = FFProbe(location).streams[0].duration - print video_length - return int(round(float(video_length))) + #video_length = FFProbe(location).streams[0].duration + #print(video_length) + return 0 def get_path_for_file(file_name): ######## returns full path for a given file name ######## for root, dirs, files in os.walk(PATH_TO_BROWSER): if file_name in files: - print root + print(root) return True, '{}/{}'.format(root,file_name) return False, '' @@ -333,7 +333,7 @@ def update_next_bank_number(new_value): global current_message memory_bank = read_json(BANK_DATA_JSON) if(memory_bank[new_value]['location'] == ''): - print 'its empty!' + print('its empty!') current_message = 'the bank you pressed is empty' else: update_json(NEXT_BANK_JSON, new_value) diff --git a/display_centre.py b/display_centre.py index 8542cfa..65d37a0 100644 --- a/display_centre.py +++ b/display_centre.py @@ -5,8 +5,9 @@ import sys import time import traceback from data_centre import * -from Tkinter import * -import tkFont +from tkinter import * +import tkinter.font +import omxdriver import video_centre import data_centre @@ -21,11 +22,11 @@ browser_start_index = 0 browser_index = 0 -print 'the start' +print('the start') try: tk = Tk() except Exception as e: - print 'failed to load tk - trying again after 2 minute' + print('failed to load tk - trying again after 2 minute') time.sleep(120) tk =Tk() @@ -36,6 +37,7 @@ video_driver = video_centre.video_driver(frame) # our data store data_object = data_centre.data() +video_player = omxdriver.omx_player(frame) browser_list = data_object.get_browser_data_for_display() @@ -60,7 +62,7 @@ def load_display(display): load_looper(display) #load_divider(display) if data_centre.current_message: - print 'trying to display' + print('trying to display') load_message(display) display.pack() @@ -223,10 +225,10 @@ load_display(display) select_current_browser_index() def key(event): - print event.char + print(event.char) ## '/' clear all banks if event.char == '/': - print 'it\'s cleared!' + print('it\'s cleared!') data_centre.clear_all_banks() refresh_display() ## '.' quits r_e_c_u_r @@ -255,6 +257,13 @@ def key(event): down_key(event) elif(event.char in ['-']): up_key(event) + ## 'enter' sets manual next flag + elif event.char in ['z']: + print('playing video') + video_player.play_video() + elif event.char in ['x']: + print('playing video') + video_player.pause_video() def up_key(event): @@ -296,7 +305,7 @@ def backspace_key(event): data_centre.switch_settings(browser_index + browser_start_index) refresh_display() except Exception as e: - print 'the current message is: {}'.format(e.message) + print('the current message is: {}'.format(e.message)) data_centre.set_message(e.message) diff --git a/display_data.json b/display_data.json index d10996c..ff2e023 100644 --- a/display_data.json +++ b/display_data.json @@ -1 +1 @@ -[{"start": -1, "length": 4, "end": -1, "location": "/media/pi/TIM1/samplerloop3s.mp4", "name": "samplerloop3s.mp4"}, {"start": -1, "length": 3, "end": -1, "location": "/media/pi/TIM1/teaser1.mp4", "name": "teaser1.mp4"}, {"start": -1, "length": 3, "end": -1, "location": "/media/pi/TIM1/teaser2.mp4", "name": "teaser2.mp4"}, {"start": -1, "length": 3, "end": -1, "location": "/media/pi/TIM1/teaser3.mp4", "name": "teaser3.mp4"}, {"start": -1, "length": 2, "end": -1, "location": "/media/pi/TIM1/teaser4.mp4", "name": "teaser4.mp4"}, {"start": -1, "length": 600, "end": -1, "location": "/media/pi/TIM1/Test.mp4", "name": "Test.mp4"}, {"start": -1, "length": 26, "end": -1, "location": "/media/pi/TIM1/TRASHPALACEVIDEOS/01_trashpalaceintro.mp4", "name": "01_trashpalaceintro.mp4"}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}, {"start": -1, "length": -1, "end": -1, "location": "", "name": ""}] \ No newline at end of file +[{"start": -1, "length": 4, "end": -1, "name": "samplerloop3s.mp4", "location": "/media/pi/TIM1/samplerloop3s.mp4"}, {"start": -1, "length": 3, "end": -1, "name": "teaser1.mp4", "location": "/media/pi/TIM1/teaser1.mp4"}, {"start": -1, "length": 3, "end": -1, "name": "teaser2.mp4", "location": "/media/pi/TIM1/teaser2.mp4"}, {"start": -1, "length": 3, "end": -1, "name": "teaser3.mp4", "location": "/media/pi/TIM1/teaser3.mp4"}, {"start": -1, "length": 2, "end": -1, "name": "teaser4.mp4", "location": "/media/pi/TIM1/teaser4.mp4"}, {"start": -1, "length": 600, "end": -1, "name": "Test.mp4", "location": "/media/pi/TIM1/Test.mp4"}, {"start": -1, "length": 26, "end": -1, "name": "01_trashpalaceintro.mp4", "location": "/media/pi/TIM1/TRASHPALACEVIDEOS/01_trashpalaceintro.mp4"}, {"start": -1, "length": 49, "end": -1, "name": "colour_pixel_01.mp4", "location": "/media/pi/TIM1/videos_to_play/colour_pixel_01.mp4"}, {"start": -1, "end": -1, "length": 0, "name": "teaser1.mp4", "location": "/media/pi/TIM1/teaser1.mp4"}, {"start": -1, "end": -1, "length": 0, "name": "samplerloop3s2.mp4", "location": "/media/pi/TIM1/samplerloop3s2.mp4"}, {"start": -1, "length": -1, "end": -1, "name": "", "location": ""}, {"start": -1, "length": -1, "end": -1, "name": "", "location": ""}, {"start": -1, "length": -1, "end": -1, "name": "", "location": ""}, {"start": -1, "length": -1, "end": -1, "name": "", "location": ""}, {"start": -1, "length": -1, "end": -1, "name": "", "location": ""}] \ No newline at end of file diff --git a/next_bank_number.json b/next_bank_number.json index c227083..c793025 100644 --- a/next_bank_number.json +++ b/next_bank_number.json @@ -1 +1 @@ -0 \ No newline at end of file +7 \ No newline at end of file diff --git a/old-omxdriver.py b/old-omxdriver.py new file mode 100644 index 0000000..6cadca5 --- /dev/null +++ b/old-omxdriver.py @@ -0,0 +1,547 @@ +import signal +import os +import sys +import dbus +import subprocess +from time import time,strftime +import data_centre + +logger = data_centre.setup_logging() + + +""" +12/6/2016 - rewrite to use dbus +2/11/2016 - connection needs to wait for dbus filemane to be populated +2/11/2016 - remove busy wait for conection +24/11/2016 - move pause after load to after first get_position to ensure omxplayer has loaded track before pause +24/11/2016 - report dbus exception messages in log +30/11/2016 - pause at start waits until position is not 0 as video has not started until it becomes -ve +2/12/2016 - make pause glitch tolerant, try again if fails +3/12/2016 - remove threading to stop pause and unpause for showing happening in wrong order +5/12/2016 - deal with situation where pause at end happened so late that video finished first +5/12/2016 - need to send nice-day when stop is received and paused for end as now do not intercept one from omxplayer + + omxdriver hides the detail of using the omxplayer command from videoplayer + This is meant to be used with videoplayer.py + Its easy to end up with many copies of omxplayer.bin running if this class is not used with care. use pp_videoplayer.py for a safer interface. + + External commands + ---------------------------- + __init__ just creates the instance and initialises variables (e.g. omx=OMXDriver()) + load - processes the track up to where it is ready to display, at this time it pauses. + show - plays the video from where 'prepare' left off by resuming from the pause. + play - plays a track (not used by gapless) + pause/unpause - pause on/off + toggle_pause - toggles pause + control - sends controls to omxplayer.bin while track is playing (use stop and pause instead of q and p) + stop - stops a video that is playing. + terminate - Stops a video playing. Used when aborting an application. + kill - kill of omxplayer when it hasn't terminated at the end of a track. + +Signals +---------- + The following signals are produced while a track is playing + self.start_play_signal = True when a track is ready to be shown + self.end_play_signal= True when a track has finished due to stop or because it has come to an end + self.end_play_reason reports the reason for the end + Also is_running() tests whether the sub-process running omxplayer is present. + +""" + +class OMXDriver(object): + + # adjust this to determine freeze after the first frame + after_first_frame_position =-50000 # microseconds + + _LAUNCH_CMD = '/usr/bin/omxplayer --no-keys ' # needs changing if user has installed his own version of omxplayer elsewhere + KEY_MAP = { '-': 17, '+': 18, '=': 18} # add more keys here, see popcornmix/omxplayer github file KeyConfig.h + + def __init__(self,widget,pp_dir): + + + self.widget=widget + self.pp_dir=pp_dir + + + self.start_play_signal=False + self.end_play_signal=False + self.end_play_reason='nothing' + self.duration=0 + self.video_position = 0 + + self.pause_at_end_required=False + self.paused_at_end=False + self.pause_at_end_time=0 + + # self.pause_before_play_required='before-first-frame' #no,before-first-frame, after-first-frame + # self.pause_before_play_required='no' + self.paused_at_start='False' + + self.paused=False + + self.terminate_reason='' + + # dbus and subprocess + self._process=None + self.__iface_root=None + self.__iface_props = None + self.__iface_player = None + + + def load(self, track, freeze_at_start,options,caller): + self.pause_before_play_required=freeze_at_start + self.caller=caller + track= "'"+ track.replace("'","'\\''") + "'" + # self.mon.log(self,'TIME OF DAY: '+ strftime("%Y-%m-%d %H:%M")) + self.dbus_user = os.environ["USER"] + + self.id=str(int(time()*10)) + + + + self.dbus_name = "org.mpris.MediaPlayer2.omxplayer"+self.id + + self.omxplayer_cmd = OMXDriver._LAUNCH_CMD + options + " --dbus_name '"+ self.dbus_name + "' " + track + # self.mon.log(self, 'dbus user ' + self.dbus_user) + # self.mon.log(self, 'dbus name ' + self.dbus_name) + + # logger.info self.omxplayer_cmd + logger.info("Send command to omxplayer: "+ self.omxplayer_cmd) + # self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/home/pi/pipresents/pp_logs/stdout.txt','a'),stderr=file('/home/pi/pipresents/pp_logs/stderr.txt','a')) + self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/dev/null','a'),stderr=file('/dev/null','a')) + self.pid=self._process.pid + + # wait for omxplayer to start then start monitoring thread + self.dbus_tries = 0 + self.omx_loaded = False + self._wait_for_dbus() + return + + def _wait_for_dbus(self): + connect_success=self.__dbus_connect() + if connect_success is True: + # print 'SUCCESS' + logger.info('connected to omxplayer dbus after ' + str(self.dbus_tries) + ' centisecs') + + # get duration of the track in microsecs if fails return a very large duration + # posibly faile because omxplayer is running but not omxplayer.bin + duration_success,duration=self.get_duration() + if duration_success is False: + logger.info('get duration failed for n attempts using '+ str(duration/60000000)+ ' minutes') + # calculate time to pause before last frame + self.duration = duration + self.pause_at_end_time = duration - 350000 + # start the thread that is going to monitor output from omxplayer. + self._monitor_status() + else: + self.dbus_tries+=1 + self.widget.after(100,self._wait_for_dbus) + + + + def _monitor_status(self): + # print '\n',self.id, '** STARTING ',self.duration + self.start_play_signal=False + self.end_play_signal=False + self.end_play_reason='nothing' + self.paused_at_end=False + self.paused_at_start='False' + self.delay = 50 + self.widget.after(0,self._status_loop) + + + + """ + freeze at start + 'no' - unpause in show - test !=0 + 'before_first_frame' - don't unpause in show, test !=0 + 'after_first_frame' - don't unpause in show, test > -100000 + """ + + def _status_loop(self): + if self.is_running() is False: + # process is not running because quit or natural end - seems not to happen + self.end_play_signal=True + self.end_play_reason='nice_day' + # print ' send nice day - process not running' + return + else: + success, video_position = self.get_position() + # if video_position <= 0: print 'read position',video_position + if success is False: + # print 'send nice day - exception when reading video position' + self.end_play_signal=True + self.end_play_reason='nice_day' + return + else: + self.video_position=video_position + # if timestamp is near the end then pause + if self.pause_at_end_required is True and self.video_position>self.pause_at_end_time: #microseconds + # print 'pausing at end, leeway ',self.duration - self.video_position + pause_end_success = self.pause(' at end of track') + if pause_end_success is True: + # print self.id,' pause for end success', self.video_position + self.paused_at_end=True + self.end_play_signal=True + self.end_play_reason='pause_at_end' + return + else: + logger.info( 'pause at end failed, probably because of delay after detection, just run on') + self.widget.after(self.delay,self._status_loop) + else: + # need to do the pausing for preload after first timestamp is received 0 is default value before start + # print self.pause_before_play_required,self.paused_at_start,self.video_position,OMXDriver.after_first_frame_position + if (self.pause_before_play_required == 'after-first-frame' and self.paused_at_start == 'False' and self.video_position >OMXDriver.after_first_frame_position)\ + or(self.pause_before_play_required != 'after-first-frame' and self.paused_at_start == 'False' and self.video_position !=0): + pause_after_load_success=self.pause('after load') + if pause_after_load_success is True: + # print self.id,' pause after load success',self.video_position + self.start_play_signal = True + self.paused_at_start='True' + else: + # should never fail, just warn at the moment + # print 'pause after load failed '+ + str(self.video_position) + logger.info( str(self.id)+ ' pause after load fail ' + str(self.video_position)) + self.widget.after(self.delay,self._status_loop) + else: + self.widget.after(self.delay,self._status_loop) + + + + def show(self,freeze_at_end_required,initial_volume): + self.initial_volume=initial_volume + self.pause_at_end_required=freeze_at_end_required + # unpause to start playing + if self.pause_before_play_required =='no': + unpause_show_success=self.unpause(' to start showing') + # print 'unpause for show',self.paused + if unpause_show_success is True: + pass + # print self.id,' unpause for show success', self.video_position + else: + # should never fail, just warn at the moment + logger.info(str(self.id)+ ' unpause for show fail ' + str(self.video_position)) + + + def control(self,char): + + val = OMXDriver.KEY_MAP[char] + #logger.info('>control received and sent to omxplayer ' + str(self.pid)) + if self.is_running(): + try: + self.__iface_player.Action(dbus.Int32(val)) + except dbus.exceptions.DBusException as ex: + #logger.info('Failed to send control - dbus exception: {}'.format(ex.get_dbus_message())) + return + else: + logger.info('Failed to send control - process not running') + return + + + # USE ONLY at end and after load + # return succces of the operation, several tries if pause did not work and no error reported. + def pause(self,reason): + #logger.info(self,'pause received {}'.format(reason)) + if self.paused is False: + logger.info('not paused so send pause {}'.format(reason)) + tries=1 + while True: + if self.send_pause() is False: + # failed for good reason + return False + status=self.omxplayer_is_paused() # test omxplayer after sending the command + if status == 'Paused': + self.paused = True + return True + if status == 'Failed': + # failed for good reason because of exception or process not running caused by end of track + return False + else: + # failed for no good reason + logger.info(self, '!!!!! repeat pause {}'.format(str(tries))) + # print self.id,' !!!!! repeat pause ',self.video_position, tries + tries +=1 + if tries >5: + # print self.id, ' pause failed for n attempts' + logger.info('pause failed for n attempts') + return False + # repeat + + + # USE ONLY for show + + def unpause(self,reason): + logger.info('MON' + 'Unpause received {}'.format(reason)) + if self.paused is True: + logger.info('MON' +'Is paused so Track will be unpaused {}'.format(reason)) + tries=1 + while True: + if self.send_unpause() is False: + return False + status = self.omxplayer_is_paused() # test omxplayer + if status == 'Playing': + self.paused = False + self.paused_at_start='done' + self.set_volume(self.initial_volume) + return True + + if status == 'Failed': + # failed for good reason because of exception or process not running caused by end of track + return False + else: + logger.info('warn' + '!!!!! repeat unpause {}'.format(tries)) + # print self.id,' !!!! repeat unpause ',self.video_position, tries + tries +=1 + if tries >5: + # print self.id, ' unpause failed for n attempts' + logger.info('warn' + 'unpause failed for n attempts') + return False + + + def omxplayer_is_paused(self): + if self.is_running(): + try: + result=self.__iface_props.PlaybackStatus() + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to test paused - dbus exception: {}'.format(ex.get_dbus_message())) + return 'Failed' + return result + else: + logger.info('warn'+'Failed to test paused - process not running') + # print self.id,' test paused not successful - process' + return 'Failed' + + + def send_pause(self): + if self.is_running(): + try: + self.__iface_player.Pause() + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to send pause - dbus exception: {}'.format(ex.get_dbus_message())) + return False + return True + else: + logger.info('warn'+'Failed to send pause - process not running') + # print self.id,' send pause not successful - process' + return False + + + def send_unpause(self): + if self.is_running(): + try: + self.__iface_player.Action(16) + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to send unpause - dbus exception: {}'.format(ex.get_dbus_message())) + return False + return True + else: + logger.info('warn'+'Failed to send unpause - process not running') + # print self.id,' send unpause not successful - process' + return False + + def pause_on(self): + logger.info('mon'+'pause on received ') + # print 'pause on',self.paused + if self.paused is True: + return + if self.is_running(): + try: + # self.__iface_player.Action(16) + self.__iface_player.Pause() # - this should work but does not!!! + self.paused=True + # print 'paused OK' + return + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to do pause on - dbus exception: {}'.format(ex.get_dbus_message())) + return + else: + logger.info('warn'+'Failed to do pause on - process not running') + return + + + def pause_off(self): + logger.info('mon'+'pause off received ') + # print 'pause off',self.paused + if self.paused is False: + return + if self.is_running(): + try: + self.__iface_player.Action(16) + self.paused=False + # print 'not paused OK' + return + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to do pause off - dbus exception: {}'.format(ex.get_dbus_message())) + return + else: + logger.info('warn'+'Failed to do pause off - process not running') + return + + def go(self): + logger.info('log'+'go received ') + self.unpause('for go') + + + def mute(self): + self.__iface_player.Mute() + + def unmute(self): + self.__iface_player.Unmute() + + def set_volume(self,millibels): + volume = pow(10, millibels / 2000.0); + self.__iface_props.Volume(volume) + + + def toggle_pause(self,reason): + logger.info('log'+'toggle pause received {}'.format(reason)) + if not self.paused: + self.paused = True + else: + self.paused=False + if self.is_running(): + try: + self.__iface_player.Action(16) + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to toggle pause - dbus exception: {}'.format(ex.get_dbus_message())) + return + else: + logger.info('warn'+'Failed to toggle pause - process not running') + return + + def run_action(self, num): + self.__iface_player.Action(num) + + def stop(self): + logger.info('log'+'>stop received and quit sent to omxplayer {} '.format(self.pid)) + # need to send 'nice day' + if self.paused_at_end is True: + self.end_play_signal=True + self.end_play_reason='nice_day' + # print 'send nice day for close track' + if self.is_running(): + try: + self.__iface_root.Quit() + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed to quit - dbus exception: {}'.format(ex.get_dbus_message())) + return + else: + logger.info('warn'+'Failed to quit - process not running') + return + + + # kill the subprocess (omxplayer and omxplayer.bin). Used for tidy up on exit. + def terminate(self,reason): + self.terminate_reason=reason + self.stop() + + def get_terminate_reason(self): + return self.terminate_reason + + + # test of whether _process is running + def is_running(self): + retcode=self._process.poll() + # print 'is alive', retcode + if retcode is None: + return True + else: + return False + + + + # kill off omxplayer when it hasn't terminated at the end of a track. + # send SIGINT (CTRL C) so it has a chance to tidy up daemons and omxplayer.bin + def kill(self): + if self.is_running()is True: + self._process.send_signal(signal.SIGINT) + + + def get_position(self): + # don't test process as is done just before + try: + micros = self.__iface_props.Position() + return True,micros + except dbus.exceptions.DBusException as ex: + # print 'Failed get_position - dbus exception: {}'.format(ex.get_dbus_message()) + return False,-1 + + + def get_duration(self): + tries=1 + while True: + success,duration=self._try_duration() + if success is True: + return True,duration + else: + logger.info('warn'+ 'repeat get duration {}'.format(tries)) + tries +=1 + if tries >5: + return False,sys.maxsize*100 + + + def _try_duration(self): + """Return the total length of the playing media""" + if self.is_running() is True: + try: + micros = self.__iface_props.Duration() + return True,micros + except dbus.exceptions.DBusException as ex: + logger.info('warn'+'Failed get duration - dbus exception: {}'.format(ex.get_dbus_message())) + return False,-1 + else: + return False,-1 + + + + + # ********************* + # connect to dbus + # ********************* + def __dbus_connect(self): + if self.omx_loaded is False: + # read the omxplayer dbus data from files generated by omxplayer + bus_address_filename = "/tmp/omxplayerdbus.{}".format(self.dbus_user) + bus_pid_filename = "/tmp/omxplayerdbus.{}.pid".format(self.dbus_user) + + if not os.path.exists(bus_address_filename): + logger.info('log'+'waiting for bus address file {}'.format(bus_address_filename)) + self.omx_loaded=False + return False + else: + f = open(bus_address_filename, "r") + bus_address = f.read().rstrip() + if bus_address == '': + logger.info('log'+ 'waiting for bus address in file {}'.format(bus_address_filename)) + self.omx_loaded=False + return False + else: + # self.mon.log(self, 'bus address found ' + bus_address) + if not os.path.exists(bus_pid_filename): + logger.info('warn'+ 'bus pid file does not exist {}'.format(bus_pid_filename)) + self.omx_loaded=False + return False + else: + f= open(bus_pid_filename, "r") + bus_pid = f.read().rstrip() + if bus_pid == '': + self.omx_loaded=False + return False + else: + # self.mon.log(self, 'bus pid found ' + bus_pid) + os.environ["DBUS_SESSION_BUS_ADDRESS"] = bus_address + os.environ["DBUS_SESSION_BUS_PID"] = bus_pid + self.omx_loaded = True + + if self.omx_loaded is True: + session_bus = dbus.SessionBus() + try: + omx_object = session_bus.get_object(self.dbus_name, "/org/mpris/MediaPlayer2", introspect=False) + self.__iface_root = dbus.Interface(omx_object, "org.mpris.MediaPlayer2") + self.__iface_props = dbus.Interface(omx_object, "org.freedesktop.DBus.Properties") + self.__iface_player = dbus.Interface(omx_object, "org.mpris.MediaPlayer2.Player") + except dbus.exceptions.DBusException as ex: + # self.mon.log(self,"Waiting for dbus connection to omxplayer: {}".format(ex.get_dbus_message())) + return False + return True + diff --git a/omxdriver.py b/omxdriver.py index afe0a3f..2b80c59 100644 --- a/omxdriver.py +++ b/omxdriver.py @@ -1,547 +1,28 @@ -import signal -import os -import sys -import dbus -import subprocess -from time import time,strftime -import data_centre - -logger = data_centre.setup_logging() - - -""" -12/6/2016 - rewrite to use dbus -2/11/2016 - connection needs to wait for dbus filemane to be populated -2/11/2016 - remove busy wait for conection -24/11/2016 - move pause after load to after first get_position to ensure omxplayer has loaded track before pause -24/11/2016 - report dbus exception messages in log -30/11/2016 - pause at start waits until position is not 0 as video has not started until it becomes -ve -2/12/2016 - make pause glitch tolerant, try again if fails -3/12/2016 - remove threading to stop pause and unpause for showing happening in wrong order -5/12/2016 - deal with situation where pause at end happened so late that video finished first -5/12/2016 - need to send nice-day when stop is received and paused for end as now do not intercept one from omxplayer - - omxdriver hides the detail of using the omxplayer command from videoplayer - This is meant to be used with videoplayer.py - Its easy to end up with many copies of omxplayer.bin running if this class is not used with care. use pp_videoplayer.py for a safer interface. - - External commands - ---------------------------- - __init__ just creates the instance and initialises variables (e.g. omx=OMXDriver()) - load - processes the track up to where it is ready to display, at this time it pauses. - show - plays the video from where 'prepare' left off by resuming from the pause. - play - plays a track (not used by gapless) - pause/unpause - pause on/off - toggle_pause - toggles pause - control - sends controls to omxplayer.bin while track is playing (use stop and pause instead of q and p) - stop - stops a video that is playing. - terminate - Stops a video playing. Used when aborting an application. - kill - kill of omxplayer when it hasn't terminated at the end of a track. - -Signals ----------- - The following signals are produced while a track is playing - self.start_play_signal = True when a track is ready to be shown - self.end_play_signal= True when a track has finished due to stop or because it has come to an end - self.end_play_reason reports the reason for the end - Also is_running() tests whether the sub-process running omxplayer is present. - -""" - -class OMXDriver(object): - - # adjust this to determine freeze after the first frame - after_first_frame_position =-50000 # microseconds - - _LAUNCH_CMD = '/usr/bin/omxplayer --no-keys ' # needs changing if user has installed his own version of omxplayer elsewhere - KEY_MAP = { '-': 17, '+': 18, '=': 18} # add more keys here, see popcornmix/omxplayer github file KeyConfig.h - - def __init__(self,widget,pp_dir): - - - self.widget=widget - self.pp_dir=pp_dir - - - self.start_play_signal=False - self.end_play_signal=False - self.end_play_reason='nothing' - self.duration=0 - self.video_position = 0 - - self.pause_at_end_required=False - self.paused_at_end=False - self.pause_at_end_time=0 - - # self.pause_before_play_required='before-first-frame' #no,before-first-frame, after-first-frame - # self.pause_before_play_required='no' - self.paused_at_start='False' - - self.paused=False - - self.terminate_reason='' - - # dbus and subprocess - self._process=None - self.__iface_root=None - self.__iface_props = None - self.__iface_player = None - - - def load(self, track, freeze_at_start,options,caller): - self.pause_before_play_required=freeze_at_start - self.caller=caller - track= "'"+ track.replace("'","'\\''") + "'" - # self.mon.log(self,'TIME OF DAY: '+ strftime("%Y-%m-%d %H:%M")) - self.dbus_user = os.environ["USER"] - - self.id=str(int(time()*10)) - - - - self.dbus_name = "org.mpris.MediaPlayer2.omxplayer"+self.id - - self.omxplayer_cmd = OMXDriver._LAUNCH_CMD + options + " --dbus_name '"+ self.dbus_name + "' " + track - # self.mon.log(self, 'dbus user ' + self.dbus_user) - # self.mon.log(self, 'dbus name ' + self.dbus_name) - - # logger.info self.omxplayer_cmd - logger.info("Send command to omxplayer: "+ self.omxplayer_cmd) - # self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/home/pi/pipresents/pp_logs/stdout.txt','a'),stderr=file('/home/pi/pipresents/pp_logs/stderr.txt','a')) - self._process=subprocess.Popen(self.omxplayer_cmd,shell=True,stdout=file('/dev/null','a'),stderr=file('/dev/null','a')) - self.pid=self._process.pid - - # wait for omxplayer to start then start monitoring thread - self.dbus_tries = 0 - self.omx_loaded = False - self._wait_for_dbus() - return - - def _wait_for_dbus(self): - connect_success=self.__dbus_connect() - if connect_success is True: - # print 'SUCCESS' - logger.info('connected to omxplayer dbus after ' + str(self.dbus_tries) + ' centisecs') - - # get duration of the track in microsecs if fails return a very large duration - # posibly faile because omxplayer is running but not omxplayer.bin - duration_success,duration=self.get_duration() - if duration_success is False: - logger.info('get duration failed for n attempts using '+ str(duration/60000000)+ ' minutes') - # calculate time to pause before last frame - self.duration = duration - self.pause_at_end_time = duration - 350000 - # start the thread that is going to monitor output from omxplayer. - self._monitor_status() - else: - self.dbus_tries+=1 - self.widget.after(100,self._wait_for_dbus) - - - - def _monitor_status(self): - # print '\n',self.id, '** STARTING ',self.duration - self.start_play_signal=False - self.end_play_signal=False - self.end_play_reason='nothing' - self.paused_at_end=False - self.paused_at_start='False' - self.delay = 50 - self.widget.after(0,self._status_loop) - - - - """ - freeze at start - 'no' - unpause in show - test !=0 - 'before_first_frame' - don't unpause in show, test !=0 - 'after_first_frame' - don't unpause in show, test > -100000 - """ - - def _status_loop(self): - if self.is_running() is False: - # process is not running because quit or natural end - seems not to happen - self.end_play_signal=True - self.end_play_reason='nice_day' - # print ' send nice day - process not running' - return - else: - success, video_position = self.get_position() - # if video_position <= 0: print 'read position',video_position - if success is False: - # print 'send nice day - exception when reading video position' - self.end_play_signal=True - self.end_play_reason='nice_day' - return - else: - self.video_position=video_position - # if timestamp is near the end then pause - if self.pause_at_end_required is True and self.video_position>self.pause_at_end_time: #microseconds - # print 'pausing at end, leeway ',self.duration - self.video_position - pause_end_success = self.pause(' at end of track') - if pause_end_success is True: - # print self.id,' pause for end success', self.video_position - self.paused_at_end=True - self.end_play_signal=True - self.end_play_reason='pause_at_end' - return - else: - logger.info( 'pause at end failed, probably because of delay after detection, just run on') - self.widget.after(self.delay,self._status_loop) - else: - # need to do the pausing for preload after first timestamp is received 0 is default value before start - # print self.pause_before_play_required,self.paused_at_start,self.video_position,OMXDriver.after_first_frame_position - if (self.pause_before_play_required == 'after-first-frame' and self.paused_at_start == 'False' and self.video_position >OMXDriver.after_first_frame_position)\ - or(self.pause_before_play_required != 'after-first-frame' and self.paused_at_start == 'False' and self.video_position !=0): - pause_after_load_success=self.pause('after load') - if pause_after_load_success is True: - # print self.id,' pause after load success',self.video_position - self.start_play_signal = True - self.paused_at_start='True' - else: - # should never fail, just warn at the moment - # print 'pause after load failed '+ + str(self.video_position) - logger.info( str(self.id)+ ' pause after load fail ' + str(self.video_position)) - self.widget.after(self.delay,self._status_loop) - else: - self.widget.after(self.delay,self._status_loop) - - - - def show(self,freeze_at_end_required,initial_volume): - self.initial_volume=initial_volume - self.pause_at_end_required=freeze_at_end_required - # unpause to start playing - if self.pause_before_play_required =='no': - unpause_show_success=self.unpause(' to start showing') - # print 'unpause for show',self.paused - if unpause_show_success is True: - pass - # print self.id,' unpause for show success', self.video_position - else: - # should never fail, just warn at the moment - logger.info(str(self.id)+ ' unpause for show fail ' + str(self.video_position)) - - - def control(self,char): - - val = OMXDriver.KEY_MAP[char] - #logger.info('>control received and sent to omxplayer ' + str(self.pid)) - if self.is_running(): - try: - self.__iface_player.Action(dbus.Int32(val)) - except dbus.exceptions.DBusException as ex: - #logger.info('Failed to send control - dbus exception: {}'.format(ex.get_dbus_message())) - return - else: - logger.info('Failed to send control - process not running') - return - - - # USE ONLY at end and after load - # return succces of the operation, several tries if pause did not work and no error reported. - def pause(self,reason): - #logger.info(self,'pause received {}'.format(reason)) - if self.paused is False: - logger.info('not paused so send pause {}'.format(reason)) - tries=1 - while True: - if self.send_pause() is False: - # failed for good reason - return False - status=self.omxplayer_is_paused() # test omxplayer after sending the command - if status == 'Paused': - self.paused = True - return True - if status == 'Failed': - # failed for good reason because of exception or process not running caused by end of track - return False - else: - # failed for no good reason - logger.info(self, '!!!!! repeat pause {}'.format(str(tries))) - # print self.id,' !!!!! repeat pause ',self.video_position, tries - tries +=1 - if tries >5: - # print self.id, ' pause failed for n attempts' - logger.info('pause failed for n attempts') - return False - # repeat - - - # USE ONLY for show - - def unpause(self,reason): - logger.info('MON' + 'Unpause received {}'.format(reason)) - if self.paused is True: - logger.info('MON' +'Is paused so Track will be unpaused {}'.format(reason)) - tries=1 - while True: - if self.send_unpause() is False: - return False - status = self.omxplayer_is_paused() # test omxplayer - if status == 'Playing': - self.paused = False - self.paused_at_start='done' - self.set_volume(self.initial_volume) - return True - - if status == 'Failed': - # failed for good reason because of exception or process not running caused by end of track - return False - else: - logger.info('warn' + '!!!!! repeat unpause {}'.format(tries)) - # print self.id,' !!!! repeat unpause ',self.video_position, tries - tries +=1 - if tries >5: - # print self.id, ' unpause failed for n attempts' - logger.info('warn' + 'unpause failed for n attempts') - return False - - - def omxplayer_is_paused(self): - if self.is_running(): - try: - result=self.__iface_props.PlaybackStatus() - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to test paused - dbus exception: {}'.format(ex.get_dbus_message())) - return 'Failed' - return result - else: - logger.info('warn'+'Failed to test paused - process not running') - # print self.id,' test paused not successful - process' - return 'Failed' - - - def send_pause(self): - if self.is_running(): - try: - self.__iface_player.Pause() - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to send pause - dbus exception: {}'.format(ex.get_dbus_message())) - return False - return True - else: - logger.info('warn'+'Failed to send pause - process not running') - # print self.id,' send pause not successful - process' - return False - - - def send_unpause(self): - if self.is_running(): - try: - self.__iface_player.Action(16) - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to send unpause - dbus exception: {}'.format(ex.get_dbus_message())) - return False - return True - else: - logger.info('warn'+'Failed to send unpause - process not running') - # print self.id,' send unpause not successful - process' - return False - - def pause_on(self): - logger.info('mon'+'pause on received ') - # print 'pause on',self.paused - if self.paused is True: - return - if self.is_running(): - try: - # self.__iface_player.Action(16) - self.__iface_player.Pause() # - this should work but does not!!! - self.paused=True - # print 'paused OK' - return - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to do pause on - dbus exception: {}'.format(ex.get_dbus_message())) - return - else: - logger.info('warn'+'Failed to do pause on - process not running') - return - - - def pause_off(self): - logger.info('mon'+'pause off received ') - # print 'pause off',self.paused - if self.paused is False: - return - if self.is_running(): - try: - self.__iface_player.Action(16) - self.paused=False - # print 'not paused OK' - return - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to do pause off - dbus exception: {}'.format(ex.get_dbus_message())) - return - else: - logger.info('warn'+'Failed to do pause off - process not running') - return - - def go(self): - logger.info('log'+'go received ') - self.unpause('for go') - - - def mute(self): - self.__iface_player.Mute() - - def unmute(self): - self.__iface_player.Unmute() - - def set_volume(self,millibels): - volume = pow(10, millibels / 2000.0); - self.__iface_props.Volume(volume) - - - def toggle_pause(self,reason): - logger.info('log'+'toggle pause received {}'.format(reason)) - if not self.paused: - self.paused = True - else: - self.paused=False - if self.is_running(): - try: - self.__iface_player.Action(16) - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to toggle pause - dbus exception: {}'.format(ex.get_dbus_message())) - return - else: - logger.info('warn'+'Failed to toggle pause - process not running') - return - - def run_action(self, num): - self.__iface_player.Action(num) - - def stop(self): - logger.info('log'+'>stop received and quit sent to omxplayer {} '.format(self.pid)) - # need to send 'nice day' - if self.paused_at_end is True: - self.end_play_signal=True - self.end_play_reason='nice_day' - # print 'send nice day for close track' - if self.is_running(): - try: - self.__iface_root.Quit() - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed to quit - dbus exception: {}'.format(ex.get_dbus_message())) - return - else: - logger.info('warn'+'Failed to quit - process not running') - return - - - # kill the subprocess (omxplayer and omxplayer.bin). Used for tidy up on exit. - def terminate(self,reason): - self.terminate_reason=reason - self.stop() - - def get_terminate_reason(self): - return self.terminate_reason - - - # test of whether _process is running - def is_running(self): - retcode=self._process.poll() - # print 'is alive', retcode - if retcode is None: - return True - else: - return False - - - - # kill off omxplayer when it hasn't terminated at the end of a track. - # send SIGINT (CTRL C) so it has a chance to tidy up daemons and omxplayer.bin - def kill(self): - if self.is_running()is True: - self._process.send_signal(signal.SIGINT) - - - def get_position(self): - # don't test process as is done just before - try: - micros = self.__iface_props.Position() - return True,micros - except dbus.exceptions.DBusException as ex: - # print 'Failed get_position - dbus exception: {}'.format(ex.get_dbus_message()) - return False,-1 - - - def get_duration(self): - tries=1 - while True: - success,duration=self._try_duration() - if success is True: - return True,duration - else: - logger.info('warn'+ 'repeat get duration {}'.format(tries)) - tries +=1 - if tries >5: - return False,sys.maxint*100 - - - def _try_duration(self): - """Return the total length of the playing media""" - if self.is_running() is True: - try: - micros = self.__iface_props.Duration() - return True,micros - except dbus.exceptions.DBusException as ex: - logger.info('warn'+'Failed get duration - dbus exception: {}'.format(ex.get_dbus_message())) - return False,-1 - else: - return False,-1 - - - - - # ********************* - # connect to dbus - # ********************* - def __dbus_connect(self): - if self.omx_loaded is False: - # read the omxplayer dbus data from files generated by omxplayer - bus_address_filename = "/tmp/omxplayerdbus.{}".format(self.dbus_user) - bus_pid_filename = "/tmp/omxplayerdbus.{}.pid".format(self.dbus_user) - - if not os.path.exists(bus_address_filename): - logger.info('log'+'waiting for bus address file {}'.format(bus_address_filename)) - self.omx_loaded=False - return False - else: - f = open(bus_address_filename, "r") - bus_address = f.read().rstrip() - if bus_address == '': - logger.info('log'+ 'waiting for bus address in file {}'.format(bus_address_filename)) - self.omx_loaded=False - return False - else: - # self.mon.log(self, 'bus address found ' + bus_address) - if not os.path.exists(bus_pid_filename): - logger.info('warn'+ 'bus pid file does not exist {}'.format(bus_pid_filename)) - self.omx_loaded=False - return False - else: - f= open(bus_pid_filename, "r") - bus_pid = f.read().rstrip() - if bus_pid == '': - self.omx_loaded=False - return False - else: - # self.mon.log(self, 'bus pid found ' + bus_pid) - os.environ["DBUS_SESSION_BUS_ADDRESS"] = bus_address - os.environ["DBUS_SESSION_BUS_PID"] = bus_pid - self.omx_loaded = True - - if self.omx_loaded is True: - session_bus = dbus.SessionBus() - try: - omx_object = session_bus.get_object(self.dbus_name, "/org/mpris/MediaPlayer2", introspect=False) - self.__iface_root = dbus.Interface(omx_object, "org.mpris.MediaPlayer2") - self.__iface_props = dbus.Interface(omx_object, "org.freedesktop.DBus.Properties") - self.__iface_player = dbus.Interface(omx_object, "org.mpris.MediaPlayer2.Player") - except dbus.exceptions.DBusException as ex: - # self.mon.log(self,"Waiting for dbus connection to omxplayer: {}".format(ex.get_dbus_message())) - return False - return True - +from omxplayer.player import OMXPlayer +from time import sleep + +class omx_player: + def __init__(self, root): + self.root = root + self.path = '/media/pi/TIM1/samplerloop3s.mp4' + self.player = None + + def play_video(self): + self.player = OMXPlayer(self.path, pause=False) + self.check_pause() + + + def check_pause(self): + current_position = self.player.position() + print('the position is {}'.format(current_position)) + if(current_position > 0): + self.player.pause() + print('its paused') + else: + self.root.after(5,self.check_pause) + + def pause_video(self): + self.player.play_pause() + + + diff --git a/video_centre.py b/video_centre.py index 9f51d39..9f97a0f 100644 --- a/video_centre.py +++ b/video_centre.py @@ -5,7 +5,7 @@ try: has_omx = True except ImportError: has_omx = False -from Tkinter import Tk, Canvas +from tkinter import Tk, Canvas import data_centre logger = data_centre.setup_logging() if data_centre.DEV_MODE == "ON": @@ -18,7 +18,7 @@ else: class video_driver(object): def __init__(self, widget=None): - print 'has omx :{}'.format(has_omx) + print('has omx :{}'.format(has_omx)) self.widget = widget self.delay = 5 logger.info('the has_omx flag is {}'.format(has_omx)) @@ -28,7 +28,7 @@ class video_driver(object): self.next_player = video_player(self.widget, 'c') self.manual_next = False - self.widget.after(self.delay, self.begin_playing) + #self.widget.after(self.delay, self.begin_playing) def begin_playing(self): # TODO: the first clip will be a demo @@ -147,13 +147,13 @@ class video_player(object): self.name,self.location )) if self.location == "" : data_centre.set_message("failed to load - bank empty") - print 'failed to load' + print('failed to load') self.failed_to_load = True else: self.omx.load(self.location, 'after-first-frame', '{} --no-osd '.format(screen_size), '') except Exception as e: - print 'load problems, the current message is: {}'.format(e.message) + print('load problems, the current message is: {}'.format(e.message)) data_centre.set_message(e.message) def get_context_for_this_player(self): @@ -188,12 +188,12 @@ class video_player(object): self.omx.stop() self.omx = OMXDriver(self.widget, '') except Exception as e: - print 'the current message is: {}'.format(e.message) + print('the current message is: {}'.format(e.message)) data_centre.set_message(e.message) def toggle_pause(self): is_paused = self.omx.omxplayer_is_paused() - print is_paused + print(is_paused) if is_paused == 'Playing': if self.omx.send_pause(): self.status = 'PAUSED' @@ -202,7 +202,7 @@ class video_player(object): if self.omx.send_unpause(): self.status = 'PLAYING' return - print 'failed to toggle pause (this needs to be in message)' + print('failed to toggle pause (this needs to be in message)') def jump_video_forward(self): self.omx.run_action(20)