Author: duncan Date: Wed Jan 10 18:28:03 2007 New Revision: 8957 Modified: branches/rel-1/freevo/src/tv/plugins/ivtv_xine_tv.py
Log: [ 1631945 ] ivtv_xine_tv update Patch from Richard van Paasen applied Modified: branches/rel-1/freevo/src/tv/plugins/ivtv_xine_tv.py ============================================================================== --- branches/rel-1/freevo/src/tv/plugins/ivtv_xine_tv.py (original) +++ branches/rel-1/freevo/src/tv/plugins/ivtv_xine_tv.py Wed Jan 10 18:28:03 2007 @@ -4,11 +4,24 @@ # ----------------------------------------------------------------------- # $Id$ # -# Author: [EMAIL PROTECTED] (rvpaasen) +# Author: +# [EMAIL PROTECTED] (Richard van Paasen) +# # Notes: +# implements live TV for IVTV class TV cards +# such as the Haupage PVR x50 series +# # Todo: +# - show OSD text once xine supports it +# http://sourceforge.net/tracker/index.php?func=detail&aid=1631022&group_id=9655&atid=359655 +# - skip to end of buffer on channel change +# http://sourceforge.net/tracker/index.php?func=detail&aid=1631019&group_id=9655&atid=359655 +# - toggle between live and record mode +# - implement progressive/accelerated seek mode +# - implement vcr mode # # ----------------------------------------------------------------------- +# # Freevo - A Home Theater PC framework # Copyright (C) 2002 Krister Lagerstrom, et al. # Please see the file freevo/Docs/CREDITS for a complete list of authors. @@ -29,7 +42,6 @@ # # ----------------------------------------------------------------------- - import config import time, os @@ -50,24 +62,94 @@ DEBUG = config.DEBUG -TRUE = 1 -FALSE = 0 +# guard important config variables if not config.XINE_TV_VO_DEV: config.XINE_TV_VO_DEV = config.XINE_VO_DEV + _debug_("XINE_TV_VO_DEV set to config.XINE_VO_DEV") + if not config.XINE_TV_AO_DEV: config.XINE_TV_AO_DEV = config.XINE_AO_DEV + _debug_("XINE_TV_AO_DEV set to config.XINE_AO_DEV") + +if not config.TV_CHANNELS: + _debug_("TV_CHANNELS is not configured!") + +if not config.TV_IN_VOLUME: + _debug_("TV_IN_VOLUME is not configured!") + +if not config.MAJOR_AUDIO_CTRL: + _debug_("MAJOR_AUDIO_CTRL is not configured!") + +if not config.XINE_COMMAND: + config.XINE_COMMAND = "xine" + _debug_("XINE_TV_CHANNELS is not configured!") + +if not config.XINE_ARGS_DEF: + config.XINE_ARGS_DEF = "" + _debug_("XINE_ARGS_DEF is not configured!") + +if not config.XINE_TV_TIMESHIFT_FILEMASK: + config.XINE_TV_TIMESHIFT_FILEMASK = "/tmp/xinebuf" + _debug_("XINE_TV_TIMESHIFT_FILEMASK is not configured!") class PluginInterface(plugin.Plugin): """ - Plugin to watch tv with xine. + Plugin to watch live tv with xine. The plugin supports: + + - Live TV: pause & continue, seek forward & backward + - Multiple digit channel selection: '1', '12, '123' + - Channel stack: jump to previously viewed channel + + Requirements: + ------------- + + The following software must be installed: + + - ivtv (e.g. Haupage x50 series TV card) + - xine (built with xvmc / xxmc if available) + + Configuration: + -------------- + + The following items should be configured in local_conf.py: + + - TV_CHANNELS + - TV_IN_VOLUME + - MAJOR_AUDIO_CTRL + - XINE_COMMAND + - XINE_ARGS_DEF + - XINE_TV_VO_DEV + - XINE_TV_AO_DEV + - XINE_TV_TIMESHIFT_FILEMASK + + To enable the channel stack, add the following event + to local_conf.py: + + EVENTS['tv']['SOME_LIRC_CMD'] = Event('POPCHANNEL') + + Refer to the config file for further explanation. """ + + __author__ = "Richard van Paasen" + __author_email__ = "[EMAIL PROTECTED]" + __maintainer__ = __author__ + __maintainer_email__ = __author_email__ + __version__ = "$Revision$" + def __init__(self): + plugin.Plugin.__init__(self) plugin.register(IVTV_XINE_TV(), plugin.TV) +# ====================================================================== + + class IVTV_XINE_TV: + """ + Main class of the plugin + """ def __init__(self): @@ -78,292 +160,391 @@ self.tuner = TunerControl() self.mixer = MixerControl() - self.app_mode = 'tv' + self.app_mode = "tv" self.app = None self.videodev = None + self.lastinput_value = None + self.lastinput_time = 0 - def Play(self, mode, tuner_channel=None, channel_change=0): - _debug_('PLAY CHAN: %s' % tuner_channel) + def Play(self, mode, channel=None, channel_change=0): + + _debug_("IVTV_XINE_TV: Play channel = '%s'" % channel) self.mode = mode self.prev_app = rc.app() rc.app(self) - self.tuner.SetChannel(self.mode, tuner_channel) + self.tuner.SetChannel(channel, True) self.mixer.prepare() self.xine.play() # Suppress annoying audio clicks time.sleep(0.6) - self.mixer.start(mode) + self.mixer.start() - _debug_('%s: started %s app' % (time.time(), self.mode)) + _debug_("IVTV_XINE_TV: Started '%s' app" % self.mode) def Pause(self): + self.xine.pause() def Stop(self): + self.mixer.stop() self.xine.stop() rc.app(self.prev_app) rc.post_event(PLAY_END) - _debug_('stopped %s app' % self.mode) + _debug_("IVTV_XINE_TV: Stopped '%s' app" % self.mode) def eventhandler(self, event, menuw=None): - _debug_('%s: %s app got %s event' % (time.time(), self.mode, event)) - s_event = '%s' % event + _debug_("IVTV_XINE_TV: '%s' app got '%s' event" % (self.mode, event)) + + s_event = "%s" % event if event == STOP or event == PLAY_END: + self.Stop() return True + if event == PAUSE or event == PLAY: + self.Pause() return True - if event in [ TV_CHANNEL_UP, TV_CHANNEL_DOWN] or s_event.startswith('INPUT_'): + + if event == "POPCHANNEL": + + self.tuner.PopChannel() + return True + + + if event in [ TV_CHANNEL_UP, TV_CHANNEL_DOWN] or s_event.startswith("INPUT_"): if event == TV_CHANNEL_UP: + + # tune next channel self.tuner.NextChannel() + elif event == TV_CHANNEL_DOWN: + + # tune previous channel self.tuner.PrevChannel() + else: - self.tuner.TuneChannel( int(s_event[6]) ) - #cmd = 'osd_show_text "%s"\n' % "CHANNEL CHANGE!" - #self.xine.app.write(cmd) + # tune explicit channel + newinput_value = int(s_event[6]) + newinput_time = int(time.time()) + + if (self.lastinput_value != 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 + + self.tuner.TuneChannel(newinput_value) + + if newinput_value > 9: + + # cancel intermediate channels + self.tuner.UnpushChannel() return True if event == SEEK: - pos = int(event.arg) + steps = int(event.arg) + + if steps == 0: + + _debug_("IVTV_XINE_TV: Ignoring seek 0") + + return True + + if steps < 0: + + action = "SeekRelative-" + steps = 0 - steps - if pos < 0: - action='SeekRelative-' - pos = 0 - pos else: - action='SeekRelative+' - if pos <= 15: - pos = 15 - elif pos <= 30: - pos = 30 + action = "SeekRelative+" + + # seeking can only be done in steps of 7, 15, 30 or 60 + + if steps <= 7: + steps = 7 + elif steps <= 15: + steps = 15 + elif steps <= 30: + steps = 30 else: - pos = 30 + steps = 60 + + _debug_("IVTV_XINE_TV: Seeking '%s%s' seconds" % (action, steps)) + + self.xine.app.write("%s%s\n" % (action, steps)) + + return True - self.xine.app.write('%s%s\n' % (action, pos)) - return TRUE if event == TOGGLE_OSD: - self.xine.app.write('OSDStreamInfos\n') + + self.xine.app.write("OSDStreamInfos\n") + return True + # ====================================================================== + class TunerControl: + """ + Class that controls the tuner device + """ def __init__(self): - self.current_vgrp = None + + self.ivtv_init = False self.fc = FreevoChannels() - self.current_chan = 0 # Current channel, index into config.TV_CHANNELS + self.curr_channel = None self.embed = None + self.stack = [ ] def _kill_(self): + if self.embed: + ivtv_dev.setvbiembed(self.embed) - def SetChannel(self, mode, channel=None): + def PushChannel(self): + + if self.curr_channel != None: + + self.stack.append(self.curr_channel) + _debug_("TunerControl: Pushed channel %s" % self.curr_channel) + + _debug_("TunerControl: Channel stack = %s" % self.stack) + + + def UnpushChannel(self): + + if len(self.stack) == 0: + + _debug_("TunerControl: Channel stack is empty") - if (channel == None): - self.current_chan = self.fc.getChannel() else: - self.current_chan = -1 - try: - for pos in range(len(config.TV_CHANNELS)): - entry = config.TV_CHANNELS[pos] - if channel == entry[2]: - channel_index = pos - self.current_chan = channel - break - except ValueError: - pass - - if (self.current_chan == -1): - _debug_("ERROR: Cannot find tuner channel '%s' in the TV channel listing" % channel) - self.current_chan = 0 - channel_index = 0 - _debug_('PLAY CHAN: %s' % self.current_chan) + channel = self.stack.pop() + _debug_("TunerControl: Unpushed channel %s" % channel) - vg = self.current_vgrp = self.fc.getVideoGroup(self.current_chan, True) - _debug_('PLAY GROUP: %s' % vg.desc) + _debug_("TunerControl: Channel stack = %s" % self.stack) - if (mode == 'tv') and (vg.group_type == 'ivtv'): - ivtv_dev = ivtv.IVTV(vg.vdev) - ivtv_dev.init_settings() - ivtv_dev.setinput(vg.input_num) - ivtv_dev.print_settings() - self.TuneChannel(channel_index + 1) - # disable embedded vbi data - self.embed = ivtv_dev.getvbiembed() - ivtv_dev.setvbiembed(0) + + def PopChannel(self): + + if len(self.stack) == 0: + + _debug_("TunerControl: Channel stack is empty") else: - _debug_('Mode "%s" is not implemented' % mode) - pop = AlertBox(text=_('This plugin only supports the ivtv video group in tv mode!')) - pop.show() - return + channel = self.stack.pop() + _debug_("TunerControl: Popped channel %s" % channel) - def NextChannel(self): - nextchan = self.fc.getNextChannel() - self.SetVideoGroup(nextchan) + self.SetVideoGroup(channel) + _debug_("TunerControl: Channel stack = %s" % self.stack) - def PrevChannel(self): - nextchan = self.fc.getPrevChannel() - self.SetVideoGroup(nextchan) + def SetChannel(self, channel=None, clearstack=False): - def TuneChannel(self, chan): - nextchan = self.fc.getManChannel(chan) - self.SetVideoGroup(nextchan) + # set channel by name + last_channel = self.curr_channel + next_channel = None + channel_index = -1 - def SetVideoGroup(self, chan): - _debug_('CHAN: %s' % chan) - vg = self.fc.getVideoGroup(chan, True) - _debug_('GROUP: %s' % vg.desc) + if clearstack == True: - if self.current_vgrp != vg: - # XXX HANDLE THIS - self.Stop(channel_change=1) - self.Play('tv', nextchan) - return + self.stack = [ ] + self.curr_channel = None - #if self.mode == 'vcr': - # # XXX HANDLE THIS - # return - - if self.current_vgrp.group_type == 'ivtv': - self.fc.chanSet(chan, True) - #self.xine.app.write('seek 999999 0\n') + if channel == None: - else: - freq_khz = self.fc.chanSet(chan, True, app=self.xine.app) - new_freq = '%1.3f' % (freq_khz / 1000.0) - #self.xine.app.write('tv_set_freq %s\n' % new_freq) - - self.current_vgrp = self.fc.getVideoGroup(self.fc.getChannel(), True) - - # Display a channel changed message (mplayer ? api osd xine ?) - tuner_id, chan_name, prog_info = self.fc.getChannelInfo() - now = time.strftime('%H:%M') - msg = '%s %s (%s): %s' % (now, chan_name, tuner_id, prog_info) - cmd = 'osd_show_text "%s"\n' % msg - #self.xine.app.write(cmd) - - - def GetChannelInfo(self): - - '''Get program info for the current channel''' - - tuner_id = config.TV_CHANNELS[self.current_chan][2] - chan_name = config.TV_CHANNELS[self.current_chan][1] - chan_id = config.TV_CHANNELS[self.current_chan][0] - - channels = epg.get_guide().GetPrograms( - start=time.time(), - stop=time.time(), chanids=[chan_id] - ) - - if channels and channels[0] and channels[0].programs: - start_s = time.strftime('%H:%M', time.localtime(channels[0].programs[0].start)) - stop_s = time.strftime('%H:%M', time.localtime(channels[0].programs[0].stop)) - ts = '(%s-%s)' % (start_s, stop_s) - prog_info = '%s %s' % (ts, channels[0].programs[0].title) + # get a channel + next_channel = self.fc.getChannel() + + try: + + # lookup the channel name in TV_CHANNELS + + for pos in range(len(config.TV_CHANNELS)): + + entry = config.TV_CHANNELS[pos] + + if channel == entry[2]: + + channel_index = pos + next_channel = channel + + break + + except ValueError: + + pass + + if (next_channel == None): + + _debug_("TunerControl: Cannot find tuner channel '%s' in the TV channel listing" % channel) else: - prog_info = 'No info' - return tuner_id, chan_name, prog_info + self.TuneChannel(channel_index + 1) + + + def TuneChannel(self, channel): + # tune channel by index -# def GetChannel(self): + next_channel = self.fc.getManChannel(channel) + _debug_("TunerControl: Explicit channel selection = '%s'" % next_channel) -# return config.TV_CHANNELS[self.current_chan][2] + self.PushChannel() + self.SetVideoGroup(next_channel) -# def NextChannel(self): + def NextChannel(self): + + next_channel = self.fc.getNextChannel() + _debug_("TunerControl: Next channel selection = '%s'" % next_channel) + + self.PushChannel() + self.SetVideoGroup(next_channel) + + + def PrevChannel(self): + + prev_channel = self.fc.getPrevChannel() + _debug_("TunerControl: Previous channel selection = '%s'" % prev_channel) + + self.PushChannel() + self.SetVideoGroup(prev_channel) + + + def SetVideoGroup(self, channel): + + _debug_("TunerControl: Play channel = '%s'" % channel) + vg = self.fc.getVideoGroup(channel, True) + _debug_("TunerControl: Play group = '%s'" % vg.desc) + + if (vg.group_type != "ivtv"): + + _debug_("TunerControl: Video group '%s' is not supported" % vg.group_type) + pop = AlertBox(text=_("This plugin only supports the ivtv video group in tv mode!")) + pop.show() + return + + if self.ivtv_init == False: + + ivtv_dev = ivtv.IVTV(vg.vdev) + ivtv_dev.init_settings() + ivtv_dev.setinput(vg.input_num) + ivtv_dev.print_settings() + + # disable embedded vbi data + self.embed = ivtv_dev.getvbiembed() + ivtv_dev.setvbiembed(0) -# self.current_chan = (self.current_chan+1) % len(config.TV_CHANNELS) + self.ivtv_init = True + self.fc.chanSet(channel, True) + self.curr_channel = channel -# def PrevChannel(self): + # todo: skip to end of stream + # todo: insert xine OSD message here ... -# self.current_chan = (self.current_chan-1) % len(config.TV_CHANNELS) + # tuner_id, chan_name, prog_info = self.fc.getChannelInfo() + # now = time.strftime("%H:%M") + # msg = "%s %s (%s): %s" % (now, chan_name, tuner_id, prog_info) + # cmd = "osd_show_text '%s'\n" % msg + # todo: insert xine OSD message here ... # ====================================================================== -class MixerControl: - # XXX Mixer manipulation code. - # TV is on line in - # VCR is mic in - # btaudio (different dsp device) will be added later +class MixerControl: + """ + Class that controls the mixer device + """ def __init__(self): - self.mixer = plugin.getbyname('MIXER') + self.mixer = plugin.getbyname("MIXER") self.volume = 0 + def prepare(self): if (self.mixer != None): - if config.MAJOR_AUDIO_CTRL == 'VOL': + if config.MAJOR_AUDIO_CTRL == "VOL": + self.volume = self.mixer.getMainVolume() self.mixer.setMainVolume(0) - elif config.MAJOR_AUDIO_CTRL == 'PCM': + + elif config.MAJOR_AUDIO_CTRL == "PCM": + self.volume = self.mixer.getPcmVolume() self.mixer.setPcmVolume(0) - def start(self, mode): + + def start(self): if (self.mixer != None): - # to be fixed... - # if self.mode == 'vcr': - if 0: - self.mixer.setMicVolume(config.VCR_IN_VOLUME) - else: - self.mixer.setLineinVolume(config.TV_IN_VOLUME) - self.mixer.setIgainVolume(config.TV_IN_VOLUME) + self.mixer.setLineinVolume(config.TV_IN_VOLUME) + self.mixer.setIgainVolume(config.TV_IN_VOLUME) + + if config.MAJOR_AUDIO_CTRL == "VOL": - if config.MAJOR_AUDIO_CTRL == 'VOL': self.mixer.setMainVolume(self.volume) - elif config.MAJOR_AUDIO_CTRL == 'PCM': + + elif config.MAJOR_AUDIO_CTRL == "PCM": + self.mixer.setPcmVolume(self.volume) def stop(self): if (self.mixer != None): + self.mixer.setLineinVolume(0) self.mixer.setMicVolume(0) - self.mixer.setIgainVolume(0) # Input on emu10k cards. + self.mixer.setIgainVolume(0) # ====================================================================== @@ -371,28 +552,30 @@ class XineApp(childapp.ChildApp2): """ - class controlling the in and output from the xine process + Class that controls the Xine process """ def __init__(self, app, item): self.item = item + _debug_("XineApp: Starting xine, cmd = '%s'" % app) childapp.ChildApp2.__init__(self, app) - _debug_('XineApp: Started, cmd=%s' % app) self.exit_type = None self.done = False def _kill_(self): - _debug_('XineApp: killed') + + _debug_("XineApp: Killing xine...") childapp.ChildApp2.kill(self,signal.SIGTERM) self.done = True + # ====================================================================== class XineThread(threading.Thread): """ - Thread to wait for a xine command to play + Thread that handles Xine commands """ def __init__(self): @@ -401,94 +584,121 @@ self.app = None self.item = None - self.state = 'idle' + self.state = "idle" self.start_flag = threading.Event() try: + xinecmd = config.XINE_COMMAND.split(' ')[0].split('/')[-1] - self.fbxine = xinecmd in ('fbxine', 'df_xine') + self.fbxine = xinecmd in ("fbxine", "df_xine") + except: - xinecmd = '' + + xinecmd = "" self.fbxine = False - _debug_( 'config.CONF.xine=%s' % (config.CONF.xine) ) - _debug_( 'config.XINE_COMMAND=%s' % (config.XINE_COMMAND) ) - _debug_( 'config.XINE_ARGS_DEF=%s' % (config.XINE_ARGS_DEF) ) - _debug_( 'config.XINE_TV_VO_DEV=%s' % (config.XINE_TV_VO_DEV) ) - _debug_( 'config.XINE_TV_AO_DEV=%s' % (config.XINE_TV_AO_DEV) ) - _debug_( 'config.XINE_TV_TIMESHIFT_FILEMASK=%s' % (config.XINE_TV_TIMESHIFT_FILEMASK) ) - _debug_( 'xinecmd=%s' % (xinecmd) ) - _debug_( 'self.fbxine=%s' % (self.fbxine) ) + _debug_( "XineThread: configuration overview" ) + _debug_( " config.CONF.xine = '%s'" % (config.CONF.xine) ) + _debug_( " config.XINE_COMMAND = '%s'" % (config.XINE_COMMAND) ) + _debug_( " config.XINE_ARGS_DEF = '%s'" % (config.XINE_ARGS_DEF) ) + _debug_( " config.XINE_TV_VO_DEV = '%s'" % (config.XINE_TV_VO_DEV) ) + _debug_( " config.XINE_TV_AO_DEV = '%s'" % (config.XINE_TV_AO_DEV) ) + _debug_( " config.XINE_TV_TIMESHIFT_FILEMASK = '%s'" % (config.XINE_TV_TIMESHIFT_FILEMASK) ) + _debug_( " effective xinecmd = '%s'" % (xinecmd) ) + _debug_( " effective self.fbxine = '%s'" % (self.fbxine) ) if self.fbxine: - self.command = '%s %s --stdctl pvr://%s' % \ + + self.command = "%s %s --stdctl pvr://%s" % \ (config.XINE_COMMAND, config.XINE_ARGS_DEF, config.XINE_TV_TIMESHIFT_FILEMASK) + else: - self.command = '%s %s -V %s -A %s --stdctl pvr://%s' % \ + + self.command = "%s %s -V %s -A %s --stdctl pvr://%s" % \ (config.XINE_COMMAND, config.XINE_ARGS_DEF, config.XINE_TV_VO_DEV, \ config.XINE_TV_AO_DEV, config.XINE_TV_TIMESHIFT_FILEMASK) def play(self): - if self.state == 'idle': + + if self.state == "idle": + self.start_flag.set() def pause(self): - if self.state == 'busy': - self.state = 'pause' + + if self.state == "busy": + + self.state = "pause" def stop(self): - if self.state == 'busy': - self.state = 'stop' + + if self.state == "busy": + + self.state = "stop" + if self.fbxine: + while not self.app.done: - _debug_('waiting for xine to end...') + + _debug_("XineThread: Waiting for xine to end...") time.sleep(0.1) - _debug_('xine ended') + + _debug_("XineThread: Xine ended") def run(self): while 1: - if self.state == 'idle': + + if self.state == "idle": + self.start_flag.wait() self.start_flag.clear() + else: + _debug_("XineThread: Should be idle on thread entry!") self.app = XineApp(self.command, self.item) - self.state = 'busy' + self.state = "busy" laststate = None + while self.app.isAlive(): + if laststate != self.state: - _debug_("state %s->%s" % (laststate, self.state)) + + _debug_("XineThread: state '%s' -> '%s'" % (laststate, self.state)) laststate = self.state - if self.state == 'busy': + if self.state == "busy": + time.sleep(0.1) - elif self.state == 'pause': - self.app.write('pause\n') - self.state = 'busy' + elif self.state == "pause": - elif self.state == 'stop': - _debug_('stoppping xine') + self.app.write("pause\n") + self.state = "busy" + + elif self.state == "stop": + + _debug_("XineThread: Stoppping xine") self.app.stop("quit\n") + if self.fbxine: + # directfb needs xine to be killed # else the display is messed up # and freevo crashes time.sleep(1.0) - _debug_('killing xine') + _debug_("XineThread: Killing xine") self.app._kill_() - #self.done = True - self.state = 'busy' - #_debug_('posting play_end') - #rc.post_event(PLAY_END) + self.state = "busy" + + _debug_("XineThread: Stopped") - _debug_('XineThread: Stopped') self.state = 'idle' ------------------------------------------------------------------------- Take Surveys. Earn Cash. Influence the Future of IT Join SourceForge.net's Techsay panel and you'll get the chance to share your opinions on IT & business topics through brief surveys - and earn cash http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV _______________________________________________ Freevo-cvslog mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/freevo-cvslog
