Beispiel #1
0
 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()
Beispiel #2
0
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
Beispiel #3
0
    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
Beispiel #4
0
    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)
Beispiel #5
0
    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
Beispiel #6
0
    @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()
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
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