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()
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
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 __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)
@threaded(MAINTHREAD) def bar(self): print 'bar is mainthread:', is_mainthread() @timed(0.1, policy=POLICY_RESTART) def poll(x): if not x: return False y = x.pop(0) print y return True @timed(0.1, OneShotTimer, policy=POLICY_RESTART) def bla(f, msg): print msg f.foo() f.bar() f = Foo() f.poll([0,1,2,3,4,5]) f.poll2(['a','b','c','d','e','f']) poll([10,11,12,13,14,15]) bla(f, 'test') bla(f, 'test2') OneShotTimer(poll, [20,21,22,23,24]).start(0.3) OneShotTimer(f.poll, [30,31,32,33,34]).start(0.3) main.run()
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
class Monitor(object): """ Monitor query for changes and call the client. """ _master = None def __init__(self, client, db, server, id, query): log.info('create new monitor %s' % id) self.id = id self._client = client self._server = server self._db = db self._query = query self._checking = False self._running = True self._check_changes = [] if not Monitor._master: Monitor._master = Master(db) Monitor._master.connect(self) self._initial_scan() if not 'parent' in query or query['parent']._beacon_id[0] != 'dir': # Generic search. We hope that everything needed ist # monitored return dirname = query['parent'].filename crawler = query['parent']._beacon_media.crawler if utils.statfs(dirname).f_type in ('nfs', 'smbfs'): # FIXME: how to get updates on directories not monitored by # inotify? Maybe poll the dirs when we have a query with # dirname it it? log.error('monitor on nfs/smbfs mountpoint to supported') return if not dirname in crawler.monitors: # Directory is not in the inotify list. Add it to make # sure changes cause a monitor update crawler.monitors.add(dirname, query['parent']) def notify_client(self, *args, **kwargs): """ Send notify rpc to client. """ try: self._client.rpc('notify', self.id, *args, **kwargs) except IOError: pass @kaa.coroutine() def check(self, changes): """ This function compares the last query result with the current db status and will inform the client when there is a change. """ if not self._running: yield True if self._checking: # Still checking. Question: What happens if new files are added # during scan? For one part, the changes here here the item changes # itself, so we would update the client all the time. So it is # better to wait here. Note: with inotify support this should not # happen often. self._check_changes.extend(changes) yield True if self._check_changes: changes = self._check_changes + changes self._check_changes = [] self._checking = True current = yield self._db.query(**self._query) self._checking = False # The query result length is different, this is a change if len(current) != len(self.items): log.info('monitor %s has changed', self.id) self.items = current self.notify_client('changed', True) yield True # Same length and length is 0. No change here if len(current) == 0: yield True # Same length, check for changes inside the items if isinstance(current[0], Item): small_changes = False for i in current: # We only compare the ids. If an item had no id before and # has now we can't detect it. But we only call this function # if we have a full scanned db. So an empty id also triggers # the update call. if not i._beacon_id: log.info('monitor %s has changed', self.id) self.items = current self.notify_client('changed', True) yield True if changes and i._beacon_id in changes: small_changes = True if small_changes: # only small stuff log.info('monitor %s has changed (internal)', self.id) self.items = current self.notify_client('changed', False) yield True log.info('monitor %s unchanged', self.id) yield True # Same length and items are not type Item. This means they are strings # from 'attr' query. last = self.items[:] for c in current: if last.pop(0) != c: self.items = current self.notify_client('changed', True) yield True yield True @kaa.coroutine(0.01) def _initial_scan(self): """ Start scanning the current list of items if they need to be updated. With a full structure covered by inotify, there should be not changes. """ self._checking = True self.items = yield self._db.query(**self._query) if not self.items or not isinstance(self.items[0], Item): self._checking = False yield False changed = [] c = 0 for i in self.items[:]: c += 1 if c > 20: # stop it and continue in the next step yield NotFinished # TODO: maybe also check parents? mtime = i._beacon_mtime if mtime != i._beacon_data.get('mtime'): changed.append(i) if not changed: # no changes but it was our first call. Tell the client that # everything is checked self.notify_client('checked') self._checking = False yield False for pos, item in enumerate(changed): self.notify_client('progress', pos + 1, len(changed), item.url) async = parse(self._db, item) if isinstance(async, kaa.InProgress): yield async if not self._running: break # commit changes so that the client may get notified self._db.commit() # The client will update its query on this signal, so it should # be safe to do the same here. *cross*fingers* self.items = yield self._db.query(**self._query) # Do not send 'changed' signal here. The db was changed and the # master notification will do the rest. Just to make sure it will # happen, start a Timer if self._check_changes: # Set new check timer. This should not be needed, but just in # case :) OneShotTimer(self.check, []).start(0.5) self.notify_client('checked') self.notify_client('changed', True) self._checking = False yield False