Exemple #1
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
Exemple #2
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
Exemple #3
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
Exemple #4
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