class PluginInterface(plugin.DaemonPlugin): """ The autostart plugin allows you to define an action that will be called when the user do not interact in the first few seconds. With this it is possible to define a default action e.g. starting radio if there is no other activity. To activate the autostart plugin add to local_conf.py:: plugin.activate('autostart') AUTOSTART_EVENTS = ( 'MENU_DOWN', 'MENU_DOWN', 'MENU_SELECT', 'MENU_SELECT', ) """ __author__ = 'Andreas Dick' __author_email__ = '*****@*****.**' __maintainer__ = __author__ __maintainer_email__ = __author_email__ __version__ = '$Revision$' def __init__(self): """ Init the autostart timeout and the plugin variables """ plugin.DaemonPlugin.__init__(self) self.active = True self.timer = OneShotTimer(self._timer_handler) self.event = EventHandler(self._event_handler) self.event.register() def config(self): return [ ('AUTOSTART_EVENTS', [], 'list of events to send to freevo at start up'), ('AUTOSTART_TIMEOUT', 5, 'Numbers of seconds to time out if there is no action'), ] def _event_handler(self, event): if self.active: if event == FREEVO_READY: self.active = True self.timer.start(config.AUTOSTART_TIMEOUT) elif event == MENU_PROCESS_END: if not self.active: self.timer.start(config.AUTOSTART_TIMEOUT) elif config.AUTOSTART_TIMEOUT: logger.info('Another event is closing the autostart plugin.') self.event.unregister() self.timer.stop() self.active = False def _timer_handler(self): if self.active: logger.info('Timeout reached without an event, posting events now.') for e in config.AUTOSTART_EVENTS: rc.post_event(Event(e)) self.event.unregister() self.timer.stop() self.active = False
class PluginInterface(plugin.Plugin): """ Native mplayer audiovisualization for Freevo. Dependant on the pygoom-2k4-0.2.0 module and goom-2k4 Activate with:: plugin.activate('audio.mplayervis') The following can be set in local_conf.py: - MPLAYERVIS_MODE Set the initial mode of the display, 0 is DOCK, 1 is FULL or 2 is NONE - MPLAYERVIS_INIT_COUNTER is the number of steps before the image fades, should be >= 255 - MPLAYERVIS_FADE_IN_WAIT_COUNTER is the number of steps to wait before cover image fades in - MPLAYERVIS_FADE_OUT_WAIT_COUNTER is the number of steps to wait before cover image fades out - MPLAYERVIS_FADE_COUNTER is the number of steps for fade transition - MPLAYERVIS_FADE_STEP is the number of steps per timer loop - MPLAYERVIS_MESSAGE_FMT is a string format for a message: - %(t)s : title - %(a)s : artist - %(l)s : album - %(n)s : trackno - %(N)s : trackof - %(y)s : year - %(g)s : genre - %(s)s : length - %(e)s : elapsed The number of steps is proportional to time of a fade transition, each step if 1/10 sec When activated the following events can be used: - DISPLAY changes the view mode - SUBTITLE toggles the title on and off - LANG toggles the message on and off (not sure if this works) - 0-9 selects the visual effect mode """ player = None visual = None view = MpvMode.DOCK vis_mode = -1 passed_event = False detached = False def __init__(self): """ Initialist the PluginInterface """ logger.debug('PluginInterface.__init__()') plugin.Plugin.__init__(self) self._type = 'mplayer_audio' self.event_context = 'audio' self.title = None self.message = None self.infodata = None self.message_fmt = config.MPLAYERVIS_MESSAGE_FMT # Event for changing between viewmodes config.EVENTS['audio']['SUBTITLE'] = Event('DISPLAY_TITLE') #'l' config.EVENTS['audio']['ENTER'] = Event('DISPLAY_MESSAGE') #'a' config.EVENTS['audio']['LANG'] = Event('DISPLAY_FPS') #'ENTER' config.EVENTS['audio']['DISPLAY'] = Event('CHANGE_MODE') #'d' config.EVENTS['audio']['+'] = Event('NEXT_VISUAL') config.EVENTS['audio']['-'] = Event('CHANGE_VISUAL', arg=-1) config.EVENTS['audio']['0'] = Event('CHANGE_VISUAL', arg=0) config.EVENTS['audio']['1'] = Event('CHANGE_VISUAL', arg=1) config.EVENTS['audio']['2'] = Event('CHANGE_VISUAL', arg=2) config.EVENTS['audio']['3'] = Event('CHANGE_VISUAL', arg=3) config.EVENTS['audio']['4'] = Event('CHANGE_VISUAL', arg=4) config.EVENTS['audio']['5'] = Event('CHANGE_VISUAL', arg=5) config.EVENTS['audio']['6'] = Event('CHANGE_VISUAL', arg=6) config.EVENTS['audio']['7'] = Event('CHANGE_VISUAL', arg=7) config.EVENTS['audio']['8'] = Event('CHANGE_VISUAL', arg=8) config.EVENTS['audio']['9'] = Event('CHANGE_VISUAL', arg=9) self.plugin_name = 'audio.mplayervis' plugin.register(self, self.plugin_name) self.mpvgoom = None self.view = MpvMode(config.MPLAYERVIS_MODE) self.view_func = [self.dock, self.fullscreen, self.noview] self.initialised = False self.timer = OneShotTimer(self._paused_handler) def config(self): """ Define the configuration variables """ logger.debug('PluginInterface.config()') return [ ('MPLAYERVIS_MODE', 0, 'Set the initial mode of the display, 0)DOCK, 1)FULL or 2)NOVI'), ('MPLAYERVIS_INIT_COUNTER', 255, 'Counter before the image fades, should be >= 255'), ('MPLAYERVIS_FADE_IN_WAIT_COUNTER', 150, 'Counter to wait before cover image fade in'), ('MPLAYERVIS_FADE_OUT_WAIT_COUNTER', 0, 'Counter to wait before cover image fade out'), ('MPLAYERVIS_FADE_COUNTER', 50, 'Counter for fade transition'), ('MPLAYERVIS_FADE_STEP', 3, 'Number of steps per timer loop'), ('MPLAYERVIS_MESSAGE_FMT', '%(t)s\n%(a)s\n%(l)s\n%(n)s\n%(s)s\n', 'Message format for the message'), ('MPLAYERVIS_FULL_GEOMETRY', '%dx%d' % (config.CONF.width, config.CONF.height), 'Full screen geometry'), ('MPLAYERVIS_FULL_ZOOM', 1, 'Fullscreen surface is zoomed by 2^ZOOM'), ('MPLAYERVIS_DOCK_ZOOM', 1, 'Docked surface is zoomed by 2^ZOOM'), ('MPLAYERVIS_FPS', 15, 'Max FPS of visualization'), ('MPLAYERVIS_MSG_FRAMES', 1000, 'Number of frames between messages in full-screen mode'), ] def toggle_view(self): """ Toggle between view modes """ logger.debug('toggle_view()') self.view += 1 if not self.mpvgoom: self.start_visual() else: self.view_func[self.view]() def eventhandler(self, event=None, arg=None): """ eventhandler to simulate hide/show of mpav """ logger.debug('mplayervis1.eventhandler(event=%r, arg=%r)', event.name, arg) print('mplayervis1.eventhandler(event=%r, arg=%r)' % (event.name, arg)) if plugin.isevent(event) == 'DETACH': PluginInterface.detached = True self.stop_visual() elif plugin.isevent(event) == 'ATTACH': PluginInterface.detached = False self.start_visual() elif event == PLAY_START: if self.player.playerGUI.succession == PlayListSuccession.FIRST: self.start_visual() else: self.resume_visual() self.message = self.item_info(self.message_fmt) if self.mpvgoom is not None: self.mpvgoom.message = self.message print('%s.message=%r' % (self.__class__, self.message)) elif event == PLAY_END: if self.player.playerGUI.succession == PlayListSuccession.LAST: self.stop_visual() else: self.pause_visual() elif event == STOP: PluginInterface.detached = False self.stop_visual() elif event == 'CHANGE_MODE': self.toggle_view() return True elif event == 'DISPLAY_FPS': if self.mpvgoom is not None: self.mpvgoom.showfps = not self.mpvgoom.showfps logger.debug('showfps=%s', self.mpvgoom.showfps) return True elif event == 'DISPLAY_TITLE': if not self.title: self.title = self.item_info('%(t)s') logger.debug('title=%s', self.title) if self.mpvgoom is not None: self.mpvgoom.set_songtitle(self.title) return True elif event == 'DISPLAY_MESSAGE': #self.message = not self.message and self.item_info(self.message_fmt) or '' if not self.message: self.message = self.item_info(self.message_fmt) logger.debug('message=%r', self.message) if self.mpvgoom is not None: self.mpvgoom.set_message(self.message) return True elif event == 'NEXT_VISUAL': PluginInterface.vis_mode += 1 if PluginInterface.vis_mode > 9: PluginInterface.vis_mode = -1 logger.debug('vis_mode=%s', PluginInterface.vis_mode) if self.mpvgoom is not None: self.mpvgoom.set_visual(PluginInterface.vis_mode) rc.post_event(Event(OSD_MESSAGE, arg=_('FXMODE is %s' % PluginInterface.vis_mode))) return True elif event == 'CHANGE_VISUAL': PluginInterface.vis_mode = event.arg if PluginInterface.vis_mode < -1: PluginInterface.vis_mode = -1 if PluginInterface.vis_mode > 9: PluginInterface.vis_mode = 9 logger.debug('vis_mode=%s', PluginInterface.vis_mode) if self.mpvgoom is not None: self.mpvgoom.set_visual(PluginInterface.vis_mode) rc.post_event(Event(OSD_MESSAGE, arg=_('FXMODE is %s' % PluginInterface.vis_mode))) return True elif event == OSD_MESSAGE: if self.mpvgoom is not None: # and self.view == MpvMode.FULL: self.mpvgoom.set_info(event.arg) return True if self.passed_event: self.passed_event = False return False self.passed_event = True return False def _paused_handler(self): """ This is only called if there is only one track to play """ logger.debug('_paused_handler') #rc.post_event does not seem to work #rc.post_event(STOP) self.stop_visual() # need to redraw the screen some how skin.redraw() def item_info(self, fmt=None): """ Set the message that scroll up the screen If the message ends with a newline then all the message lines will scroll off the top of the screen. Otherwise the last line of message will stay in the middle of the screen. @param fmt: message format string @returns: info about the current playing song """ logger.debug('item_info(fmt=%r)', fmt) if fmt is None: return '' #print('self.item=\n%s' % (pformat(self.item.__dict__),)) #print('self.item.info=\n%s' % (pformat(self.item.info.__dict__),)) item = self.item info = item.info image = item.image song = { 't': item.title if hasattr(item, 'title') else info['title'] if 'title' in info else item.name, 'a': item.artist if hasattr(item, 'artist') else info['artist'], 'l': item.album if hasattr(item, 'album') else info['album'], 'n': item.trackno if hasattr(item, 'trackno') else info['trackno'], 'N': item.trackof if hasattr(item, 'trackof') else info['trackof'], 'y': item.userdate if hasattr(item, 'userdate') else info['userdate'] if 'userdate' in info \ else info['year'], 'g': item.genre if hasattr(item, 'genre') else item['genre'], 's': '%i:%02i' % (int(item.length/60), int(item.length%60)) if item.length else '', 'e': '%i:%02i' % (int(item.elapsed/60), int(item.elapsed%60)) if item.elapsed else '', } print('song=\n%s' % (pformat(song),)) message_parts = [] keys = fmt.split('\n') #print 'keys=%r' % (keys,) for key in keys: if key == '': message_parts.append('') try: message_part = key % song if message_part: message_parts.append(message_part) except KeyError: print('unknown key %r' % (key,)) message = '\n'.join(message_parts) #print 'message=%r' % (message,) # don't like this, remove it #if self.mpvgoom is not None: # self.mpvgoom.message_counter = 1 # self.mpvgoom.message = message logger.debug('item_info: message=%r', message) return message def dock(self): logger.debug('dock()') self.mpvgoom.mode = MpvMode.DOCK if rc.app() != self.player.eventhandler: rc.app(self.player) self.mpvgoom.set_dock() if not self.player.playerGUI.visible: osd.active = True skin.resume() self.player.playerGUI.show() def fullscreen(self): logger.debug('fullscreen()') self.mpvgoom.mode = MpvMode.FULL if self.player.playerGUI.visible: self.player.playerGUI.hide() osd.active = False self.mpvgoom.set_fullscreen() skin.clear() skin.suspend() rc.app(self) def noview(self): logger.debug('noview()') self.mpvgoom.mode = MpvMode.NOVI if rc.app() != self.player.eventhandler: rc.app(self.player) if self.mpvgoom is not None: self.stop_visual() if not self.player.playerGUI.visible: osd.active = True skin.resume() self.player.playerGUI.show() def start_visual(self): logger.debug('%s.start_visual() self.view=%r self.succession=%r', self.__class__, self.view, self.player.playerGUI.succession) #if self.player.playerGUI.succession != PlayListSuccession.FIRST: # return self.timer.stop() if self.mpvgoom is not None and self.mpvgoom.running: return if self.view == MpvMode.NOVI: return if rc.app() == self.player.eventhandler: title = self.item.title if hasattr(self.item, 'title') and self.item.title else self.item.name self.mpvgoom = MpvGoom(300, 300, 150, 150, title, self.item.image) if self.mpvgoom is None: raise Exception('Cannot initialise MpvGoom') #if self.view == MpvMode.FULL: self.mpvgoom.set_info(self.item.name, 10) self.title = None self.message = None logger.debug('self.mpvgoom.running=%r -> True', self.mpvgoom.running) self.mpvgoom.running = True self.view_func[self.view]() self.mpvgoom.start() self.mpvgoom.timer.start(1.0 / config.MPLAYERVIS_FPS) if self.view == MpvMode.FULL: skin.suspend() def pause_visual(self): logger.debug('%s.pause_visual() self.view=%r self.succession=%r', self.__class__, self.view, self.player.playerGUI.succession) self.timer.start(5) def resume_visual(self): logger.debug('%s.resume_visual() self.view=%r self.succession=%r', self.__class__, self.view, self.player.playerGUI.succession) self.timer.stop() if self.mpvgoom is not None: self.title = None self.message = self.item_info(self.message_fmt) if self.view == MpvMode.FULL: skin.clear() else: self.start_visual() def stop_visual(self): logger.debug('%s.stop_visual() self.view=%r self.succession=%r', self.__class__, self.view, self.player.playerGUI.succession) self.timer.stop() if self.mpvgoom is not None: logger.debug('self.mpvgoom.running=%r -> False', self.mpvgoom.running) self.mpvgoom.timer.stop() self.mpvgoom.running = False self.mpvgoom.remove() self.mpvgoom = None self.goom = None osd.active = True skin.resume() def play(self, command, player): """ Play the track @param command: mplayer command @param player: the player object """ logger.debug('%s.play(command=%r, player=%r)', self.__class__, command, player) self.player = player self.item = player.playerGUI.item if self.mpvgoom is not None: title = self.item.title if hasattr(self.item, 'title') else self.item.name self.mpvgoom.set_songtitle(title) self.mpvgoom.set_coverimage(self.item.image) #if config.MPLAYERVIS_HAS_TRACK: # return command + [ '-af', 'export=%s' % MMAP_FILE + ',track=5:1500' ] return command + [ '-af', 'export=%s' % MMAP_FILE ] def stop(self): logger.debug('%s.stop()', self.__class__) def stdout(self, line): """ get information from mplayer stdout It should be safe to do call start() from here since this is now a callback from main. """ logger.log( 9, 'stdout(line=%r)', line) if PluginInterface.detached: return memory_mapped = False if line.find('[export] Memory mapped to file: ' + MMAP_FILE) == 0: memory_mapped = True logger.debug("Detected MPlayer 'export' audio filter! Using MPAV.") #if not self.mpvgoom.running: # if memory_mapped and not self.mpvgoom: # self.start_visual() return
class XineIvtv: """ Main class of the plugin """ def __init__(self): """ XineIvtv constructor """ self.xine = XineControl() self.tuner = TunerControl(self.xine) self.mixer = MixerControl(self.xine) self.timer = OneShotTimer(self.TimerHandler) self.app = None self.prev_app = None self.event_context = 'tv' self.lastinput_time = 0 self.lastinput_value = None self.seeksteps = 0 self.seektime_start = 0 self.seektime_previous = 0 self.seekevent_previous = None self.confirmstop_time = 0 self.recordmenu = False self.recordmode = -1 def ConfirmStop(self, msg): """ confirm stop event """ confirmstop_time = int(time.time()) # note: the OSD msg is displayed for 5 seconds if config.XINE_TV_CONFIRM_STOP and (confirmstop_time - self.confirmstop_time) > 4: self.xine.ShowMessage(msg) self.confirmstop_time = confirmstop_time return False else: return True def InitLiveRecording(self): """ start timer to mark show changes """ self.StartTimer() def StartLiveRecording(self, event): """ start live recording """ if event == INPUT_1: # record from this point on self.recordmode = 0 name = self.tuner.GetChannelName() start = time.localtime() self.StopTimer() logger.debug('XineIvtv.StartLiveRecording: Record from now on') elif event == INPUT_2: # record from start of show self.recordmode = 1 start_t, stop_t, name = self.tuner.GetInfo() start = time.localtime(start_t) self.StartTimer() logger.debug('XineIvtv.StartLiveRecording: Record from show start') elif event == INPUT_3: # record from start of stream self.recordmode = 2 name = self.tuner.GetChannelName() start = time.localtime() self.StopTimer() logger.debug('XineIvtv.StartLiveRecording: Record from stream start') if self.recordmode in [ 0, 1, 2 ]: # create fil ename and kick xine filename_array = { 'progname': String(name), 'title': String('') } filemask = config.TV_RECORD_FILE_MASK % filename_array filemask = time.strftime(filemask, start) filename = tvutil.progname2filename(filemask).rstrip(' -_:') self.xine.Record(self.recordmode, filename) self.xine.ShowMessage(_('Recording: %s' % String(name))) logger.debug('XineIvtv.StartLiveRecording: filename=%s', String(filename)) def StopLiveRecording(self): """ stop the current live recording """ self.xine.SetMark() self.xine.ShowMessage(_('Recording ended')) self.recordmode = -1 def StartTimer(self): """ program the timer to start of next show on the current channel """ logger.debug('XineIvtv.StartTimer: Start timer') # set timer to mark the next program start_t, stop_t, prog_s = self.tuner.GetInfo() if stop_t > 0: stop_t = stop_t - time.time() if self.recordmode in [ 1 ]: # add some padding in show record mode stop_t = stop_t + config.TV_RECORD_PADDING_POST self.timer.start(stop_t) logger.debug('XineIvtv.StartTimer: Timer set to mark next program in: %s seconds', stop_t) else: self.timer.stop() logger.debug('XineIvtv.StartTimer: Timer not set, stop_t not available') self.tuner.ShowInfo() def StopTimer(self): """ stop timer """ logger.debug('XineIvtv.StopTimer: Stop timer') self.timer.stop() def TimerHandler(self): """ handle timer event """ logger.debug('XineIvtv.TimerHandler: Timer event, mark new show') if self.recordmode in [ 1 ]: # stop recording when recording in show mode self.StopLiveRecording() else: self.xine.SetMark() self.StartTimer() def Play(self, mode, channel=None, channel_change=0): """ Start the xine player """ logger.debug('XineIvtv.Play(mode=%r, channel=%r, channel_change=%r)', mode, channel, channel_change) self.mode = mode rc.add_app(self) self.mixer.Mute() self.xine.Start() self.tuner.SetChannelByName(channel, True) # Suppress annoying audio clicks time.sleep(0.6) self.mixer.UnMute() if config.XINE_TV_LIVE_RECORD: self.InitLiveRecording() else: self.tuner.ShowInfo() dialog.enable_overlay_display(AppTextDisplay(self.xine.ShowMessage)) logger.debug('Started %r app', self.mode) def Stop(self): """ Stop the xine player """ logger.debug('XineIvtv.Stop()') dialog.disable_overlay_display() self.mixer.Stop() self.tuner.Stop() self.xine.Stop() rc.remove_app(self) rc.post_event(PLAY_END) logger.debug('Stopped %r app', self.mode) def eventhandler(self, event, menuw=None): """ Event handler """ logger.debug('XineIvtv.eventhandler(event=%r)', event.name) if self.recordmode in [ 0, 1, 2 ]: # handle event while live recording is active if event in [ TV_START_RECORDING, STOP ]: if self.ConfirmStop(_('Live Recording active, please repeat to stop')): self.StopLiveRecording() self.StartTimer() return True elif event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: self.xine.ShowMessage(_('Recording in progress!')) return True if self.recordmenu: # handle event while menu mode is displayed if event in [ INPUT_1, INPUT_2, INPUT_3 ]: self.StartLiveRecording(event) self.xine.HideMenu() self.recordmenu = False return True elif event in [ TV_START_RECORDING, STOP ]: self.xine.HideMenu() self.recordmenu = False return True elif event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: # ignore these commands in record menu mode return True if event in [ TV_START_RECORDING ]: if config.XINE_TV_LIVE_RECORD: self.xine.ShowMenu("Start Recording~From now on~Current show~Everything") self.recordmenu = True else: self.xine.ShowMessage(_('Live Recording is not enabled')) return True if event in [ STOP, PLAY_END ]: if self.ConfirmStop(_('Please repeat to stop')): self.Stop() if event in [ PAUSE, PLAY ]: self.xine.Pause() if event in [ 'POPCHANNEL' ]: self.tuner.PopChannel() if event in [ 'TOGGLECHANNEL' ]: self.tuner.SwapChannel() return True if event in [ TV_CHANNEL_UP ]: # tune next channel self.tuner.NextChannel() if event in [ TV_CHANNEL_DOWN ]: # tune previous channel self.tuner.PrevChannel() if event in [ INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: s_event = '%r' % event eventInput=s_event[6] isNumeric=TRUE try: newinput_value = int(eventInput) except: # Protected against INPUT_UP, INPUT_DOWN, etc isNumeric=FALSE if isNumeric: # tune explicit channel newinput_time = int(time.time()) if self.lastinput_value is not None: # allow 2 seconds delay for multiple digit channels if newinput_time - self.lastinput_time < 2: # this enables multiple (max 3) digit channel selection if self.lastinput_value >= 100: self.lastinput_value = (self.lastinput_value % 100) newinput_value = self.lastinput_value * 10 + newinput_value self.lastinput_value = newinput_value self.lastinput_time = newinput_time if config.XINE_TV_INPUT_REAL_CHANNELS: self.tuner.SetChannelByNumber(newinput_value) else: self.tuner.SetChannelByIndex(newinput_value, 1) if newinput_value > 9: # cancel intermediate channels self.tuner.UnpushChannel() if event in [ SEEK ]: seeksteps = int(event.arg) direction = 0 if seeksteps == 0: logger.debug('Ignoring seek 0') else: if seeksteps < 0: direction = -1 seeksteps = 0 - seeksteps else: direction = +1 if config.XINE_TV_PROGRESSIVE_SEEK: seekevent_current = event.arg seeksteps_current = seeksteps seektime_current = int(time.time()) seektime_delta = seektime_current - self.seektime_previous if seektime_delta > 2 or seekevent_current != self.seekevent_previous: # init/reset progressive seek mode self.seeksteps = seeksteps self.seektime_start = seektime_current self.seektime_previous = seektime_current else: # continue progressive seek mode if seektime_delta > 0: if (seektime_delta % config.XINE_TV_PROGRESSIVE_SEEK_THRESHOLD) == 0: self.seeksteps += config.XINE_TV_PROGRESSIVE_SEEK_INCREMENT self.seektime_previous = seektime_current self.seekevent_previous = seekevent_current seeksteps = self.seeksteps # Note: Xine 2007 versions supports # arbitrary SeekRelative+/- steps # limit seeksteps to [1 ; 120] seconds seeksteps = min( max(1, seeksteps), 120 ) self.xine.Seek(direction, seeksteps) if event in [ TOGGLE_OSD ]: self.tuner.ShowInfo() if event in [ OSD_MESSAGE ]: self.xine.ShowMessage(event.arg) if config.XINE_TV_LIVE_RECORD: if event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: # explicitly mark video stream self.xine.SetMark() # start timer to mark next show self.StartTimer() return True
class XineIvtv: """ Main class of the plugin """ def __init__(self): """ XineIvtv constructor """ self.xine = XineControl() self.tuner = TunerControl(self.xine) self.mixer = MixerControl(self.xine) self.timer = OneShotTimer(self.TimerHandler) self.app = None self.prev_app = None self.event_context = 'tv' self.lastinput_time = 0 self.lastinput_value = None self.seeksteps = 0 self.seektime_start = 0 self.seektime_previous = 0 self.seekevent_previous = None self.confirmstop_time = 0 self.recordmenu = False self.recordmode = -1 def ConfirmStop(self, msg): """ confirm stop event """ confirmstop_time = int(time.time()) # note: the OSD msg is displayed for 5 seconds if config.XINE_TV_CONFIRM_STOP and (confirmstop_time - self.confirmstop_time) > 4: self.xine.ShowMessage(msg) self.confirmstop_time = confirmstop_time return False else: return True def InitLiveRecording(self): """ start timer to mark show changes """ self.StartTimer() def StartLiveRecording(self, event): """ start live recording """ if event == INPUT_1: # record from this point on self.recordmode = 0 name = self.tuner.GetChannelName() start = time.localtime() self.StopTimer() logger.debug('XineIvtv.StartLiveRecording: Record from now on') elif event == INPUT_2: # record from start of show self.recordmode = 1 start_t, stop_t, name = self.tuner.GetInfo() start = time.localtime(start_t) self.StartTimer() logger.debug('XineIvtv.StartLiveRecording: Record from show start') elif event == INPUT_3: # record from start of stream self.recordmode = 2 name = self.tuner.GetChannelName() start = time.localtime() self.StopTimer() logger.debug( 'XineIvtv.StartLiveRecording: Record from stream start') if self.recordmode in [0, 1, 2]: # create fil ename and kick xine filename_array = {'progname': String(name), 'title': String('')} filemask = config.TV_RECORD_FILE_MASK % filename_array filemask = time.strftime(filemask, start) filename = tvutil.progname2filename(filemask).rstrip(' -_:') self.xine.Record(self.recordmode, filename) self.xine.ShowMessage(_('Recording: %s' % String(name))) logger.debug('XineIvtv.StartLiveRecording: filename=%s', String(filename)) def StopLiveRecording(self): """ stop the current live recording """ self.xine.SetMark() self.xine.ShowMessage(_('Recording ended')) self.recordmode = -1 def StartTimer(self): """ program the timer to start of next show on the current channel """ logger.debug('XineIvtv.StartTimer: Start timer') # set timer to mark the next program start_t, stop_t, prog_s = self.tuner.GetInfo() if stop_t > 0: stop_t = stop_t - time.time() if self.recordmode in [1]: # add some padding in show record mode stop_t = stop_t + config.TV_RECORD_PADDING_POST self.timer.start(stop_t) logger.debug( 'XineIvtv.StartTimer: Timer set to mark next program in: %s seconds', stop_t) else: self.timer.stop() logger.debug( 'XineIvtv.StartTimer: Timer not set, stop_t not available') self.tuner.ShowInfo() def StopTimer(self): """ stop timer """ logger.debug('XineIvtv.StopTimer: Stop timer') self.timer.stop() def TimerHandler(self): """ handle timer event """ logger.debug('XineIvtv.TimerHandler: Timer event, mark new show') if self.recordmode in [1]: # stop recording when recording in show mode self.StopLiveRecording() else: self.xine.SetMark() self.StartTimer() def Play(self, mode, channel=None, channel_change=0): """ Start the xine player """ logger.debug('XineIvtv.Play(mode=%r, channel=%r, channel_change=%r)', mode, channel, channel_change) self.mode = mode rc.add_app(self) self.mixer.Mute() self.xine.Start() self.tuner.SetChannelByName(channel, True) # Suppress annoying audio clicks time.sleep(0.6) self.mixer.UnMute() if config.XINE_TV_LIVE_RECORD: self.InitLiveRecording() else: self.tuner.ShowInfo() dialog.enable_overlay_display(AppTextDisplay(self.xine.ShowMessage)) logger.debug('Started %r app', self.mode) def Stop(self): """ Stop the xine player """ logger.debug('XineIvtv.Stop()') dialog.disable_overlay_display() self.mixer.Stop() self.tuner.Stop() self.xine.Stop() rc.remove_app(self) rc.post_event(PLAY_END) logger.debug('Stopped %r app', self.mode) def eventhandler(self, event, menuw=None): """ Event handler """ logger.debug('XineIvtv.eventhandler(event=%r)', event.name) if self.recordmode in [0, 1, 2]: # handle event while live recording is active if event in [TV_START_RECORDING, STOP]: if self.ConfirmStop( _('Live Recording active, please repeat to stop')): self.StopLiveRecording() self.StartTimer() return True elif event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: self.xine.ShowMessage(_('Recording in progress!')) return True if self.recordmenu: # handle event while menu mode is displayed if event in [INPUT_1, INPUT_2, INPUT_3]: self.StartLiveRecording(event) self.xine.HideMenu() self.recordmenu = False return True elif event in [TV_START_RECORDING, STOP]: self.xine.HideMenu() self.recordmenu = False return True elif event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: # ignore these commands in record menu mode return True if event in [TV_START_RECORDING]: if config.XINE_TV_LIVE_RECORD: self.xine.ShowMenu( "Start Recording~From now on~Current show~Everything") self.recordmenu = True else: self.xine.ShowMessage(_('Live Recording is not enabled')) return True if event in [STOP, PLAY_END]: if self.ConfirmStop(_('Please repeat to stop')): self.Stop() if event in [PAUSE, PLAY]: self.xine.Pause() if event in ['POPCHANNEL']: self.tuner.PopChannel() if event in ['TOGGLECHANNEL']: self.tuner.SwapChannel() return True if event in [TV_CHANNEL_UP]: # tune next channel self.tuner.NextChannel() if event in [TV_CHANNEL_DOWN]: # tune previous channel self.tuner.PrevChannel() if event in [ INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: s_event = '%r' % event eventInput = s_event[6] isNumeric = TRUE try: newinput_value = int(eventInput) except: # Protected against INPUT_UP, INPUT_DOWN, etc isNumeric = FALSE if isNumeric: # tune explicit channel newinput_time = int(time.time()) if self.lastinput_value is not None: # allow 2 seconds delay for multiple digit channels if newinput_time - self.lastinput_time < 2: # this enables multiple (max 3) digit channel selection if self.lastinput_value >= 100: self.lastinput_value = (self.lastinput_value % 100) newinput_value = self.lastinput_value * 10 + newinput_value self.lastinput_value = newinput_value self.lastinput_time = newinput_time if config.XINE_TV_INPUT_REAL_CHANNELS: self.tuner.SetChannelByNumber(newinput_value) else: self.tuner.SetChannelByIndex(newinput_value, 1) if newinput_value > 9: # cancel intermediate channels self.tuner.UnpushChannel() if event in [SEEK]: seeksteps = int(event.arg) direction = 0 if seeksteps == 0: logger.debug('Ignoring seek 0') else: if seeksteps < 0: direction = -1 seeksteps = 0 - seeksteps else: direction = +1 if config.XINE_TV_PROGRESSIVE_SEEK: seekevent_current = event.arg seeksteps_current = seeksteps seektime_current = int(time.time()) seektime_delta = seektime_current - self.seektime_previous if seektime_delta > 2 or seekevent_current != self.seekevent_previous: # init/reset progressive seek mode self.seeksteps = seeksteps self.seektime_start = seektime_current self.seektime_previous = seektime_current else: # continue progressive seek mode if seektime_delta > 0: if (seektime_delta % config.XINE_TV_PROGRESSIVE_SEEK_THRESHOLD ) == 0: self.seeksteps += config.XINE_TV_PROGRESSIVE_SEEK_INCREMENT self.seektime_previous = seektime_current self.seekevent_previous = seekevent_current seeksteps = self.seeksteps # Note: Xine 2007 versions supports # arbitrary SeekRelative+/- steps # limit seeksteps to [1 ; 120] seconds seeksteps = min(max(1, seeksteps), 120) self.xine.Seek(direction, seeksteps) if event in [TOGGLE_OSD]: self.tuner.ShowInfo() if event in [OSD_MESSAGE]: self.xine.ShowMessage(event.arg) if config.XINE_TV_LIVE_RECORD: if event in [ 'POPCHANNEL', TV_CHANNEL_UP, TV_CHANNEL_DOWN, INPUT_0, INPUT_1, INPUT_2, INPUT_3, INPUT_4, INPUT_5, INPUT_6, INPUT_7, INPUT_8, INPUT_9 ]: # explicitly mark video stream self.xine.SetMark() # start timer to mark next show self.StartTimer() return True