I got my second PVR card. So I now have a hauppauge PVR-350 and a PVR-150.

I hacked recordserver and ivtv_record (and a bit in channels) so make it
so that I can now record two channels at the same time!
 
I must say the hack was rather straightforward due to the good structure
of freevo! (kudos to the authors!)

The files are attached and will give the desired result with the
following in local_conf.py:

VIDEO_GROUPS = [
   VideoGroup(vdev="/dev/video0",
      adev=None,
      input_type='tuner',
#      input_num=0,
      tuner_norm=CONF.tv,
      tuner_chanlist=CONF.chanlist,
      desc='Cable',
      group_type='ivtv',
      record_group=None),
  VideoGroup(vdev="/dev/video1",
      adev=None,
      input_type='tuner',
#      input_num=0,
      tuner_norm=CONF.tv,
      tuner_chanlist=CONF.chanlist,
      desc='Cable',
      group_type='ivtv',
        record_group=None)
   ]

Now I must say that I have cut some corners to get to this result this
quickly.

1) I assumed all cards are of the same type (ivtv).
2) I assumed conflicts would occur rarely now, so I simply kill the
first show on the first card in case of conflicts.
3) I don't watch TV through freevo so I am not too fussed if there now
is undesired interaction between TV viewing and recording (along the
lines of the lockfiles, I am sure I broke something there)

So I do not recommend these changes for inclusion into general codebase
as is. It will need some cleanup to make less assumptions about the
environment..

For the time being I plan to see if this works in practice. Anyone who
wishes to try the code just move the files attached to the correct
places (of course save the originals!!) and go ahead.

channels lives in tv
ivtv_record lives in tv/plugins
recordserver in helpers

enjoy

Paul

-- 
Paul Sijben             mailto:[EMAIL PROTECTED]
Amersfoort, NL          http://www.sijben.net
tel:+31 334557522       fax:+31 33 4557523

# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# channels.py - Freevo module to handle channel changing.
# -----------------------------------------------------------------------
# $Id: channels.py 8347 2006-10-11 08:37:57Z duncan $
#
# Notes:
# Todo:        
#
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2003 Krister Lagerstrom, et al. 
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# ----------------------------------------------------------------------- */


import config, plugin
import tv.freq, tv.v4l2
import epg_xmltv
import threading
import time

DEBUG = config.DEBUG

# Sample for local_conf.py:
# Three video cards and one web camera.
#VIDEO_GROUPS = [
#    VideoGroup(vdev='/dev/video0',
#               adev=None,
#               input_type='tuner',
#               tuner_type='external',
#               tuner_chan='3',
#               desc='Bell ExpressVu (for playing)',
#               record_group=1),
#    VideoGroup(vdev='/dev/video1',
#               adev=None,
#               input_type='tuner',
#               tuner_type='external',
#               tuner_chan='3',
#               desc='Bell ExpressVu (for recording)',
#               record_group=None),
#    VideoGroup(vdev='/dev/video2',
#               adev='/dev/dsp1',
#               input_type='tuner',
#               desc='ATI TV-Wonder (both playing and recording)',
#               record_group=None),
#    VideoGroup(vdev='/dev/video3',
#               adev=None,
#               input_type='webcam',
#               desc='Logitech Quickcam',
#               record_group=None),
#]

class FreevoChannels:

    def __init__(self):
        self.chan_index = 0
        self.lock = threading.Lock()

        if config.plugin_external_tuner: 
            plugin.init_special_plugin(config.plugin_external_tuner)


    def getVideoGroup(self, chan, isplayer):
        """
        Gets the VideoGroup object used by this Freevo channel.
        """
        try:
            self.lock.acquire()
            group = 0

            for i in range(len(config.TV_CHANNELS)):
                chan_info = config.TV_CHANNELS[i]
                if chan_info[2] == chan:
                    try:
                        group = int(chan_info[4])
                    except: # XXX: put a better exception here
                        group = 0
            if not isplayer:
                record_group = config.VIDEO_GROUPS[group].record_group
                if record_group:
                    try:
                        # some simple checks
                        group = int(record_group)
                        record_vg = config.VIDEO_GROUPS[group]
                    except:
                        print 'VIDEO_GROUPS[%s].record_group=%s is invalid' % 
(group, record_group)
        finally:
            self.lock.release()

        return config.VIDEO_GROUPS[group]


    def chanUp(self, isplayer, app=None, app_cmd=None):
        """
        Using this method will not support custom frequencies.
        """
        return self.chanSet(self.getNextChannel(), isplayer, app, app_cmd)


    def chanDown(self, isplayer, app=None, app_cmd=None):
        """
        Using this method will not support custom frequencies.
        """
        return self.chanSet(self.getPrevChannel(), isplayer, app, app_cmd)


    def chanSet(self, chan, isplayer, app=None, app_cmd=None,vg=None):
        new_chan = None

        for pos in range(len(config.TV_CHANNELS)):
            chan_cfg = config.TV_CHANNELS[pos]
            if chan_cfg[2] == chan:
                new_chan = chan
                self.chan_index = pos

        if not new_chan:
            print String(_('ERROR')+': '+\
                        (_('Cannot find tuner channel "%s" in the TV channel 
listing') % chan))
            return
        if not vg:
                vg = self.getVideoGroup(new_chan, isplayer)

        if vg.tuner_type == 'external':
            tuner = plugin.getbyname('EXTERNAL_TUNER')
            if tuner:
                tuner.setChannel(new_chan)

            if vg.input_type == 'tuner' and vg.tuner_chan:
                freq = self.tunerSetFreq(vg.tuner_chan, app, app_cmd)
                return freq

            return 0

        else:
            return self.tunerSetFreq(chan, isplayer, app, app_cmd,vg)

        return 0


    def tunerSetFreq(self, chan, isplayer, app=None, app_cmd=None,vg=None):
        chan = str(chan)
        if not vg:
                vg = self.getVideoGroup(chan, isplayer)

        freq = config.FREQUENCY_TABLE.get(chan)
        if freq:
            if DEBUG:
                print String('USING CUSTOM FREQUENCY: chan="%s", freq="%s"'% \
                      (chan, freq))
        else:
            clist = tv.freq.CHANLIST.get(vg.tuner_chanlist)
            if clist:
                freq = clist.get(chan)
            else:
                if vg.group_type != 'dvb':
                    print String(_('ERROR')+': ' + \
                                 (_('Unable to get channel list for %s.') % \
                                  vg.tuner_chanlist))
                return 0
            if not freq:
                if vg.group_type != 'dvb':
                    print String(_('ERROR')+': ' + \
                                 (_('Unable to get channel list for %s.') % \
                                  vg.tuner_chanlist))
                return 0
            if DEBUG:
                print String('USING STANDARD FREQUENCY: chan="%s", freq="%s"' % 
\
                      (chan, freq))

        if app:
            if app_cmd:
                self.appSend(app, app_cmd)
            else:
                # If we have app and not app_cmd we return the frequency so
                # the caller (ie: mplayer/tvtime/mencoder plugin) can set it
                # or provide it on the command line.
                return freq
        else:
            # XXX: add code here for TUNER_LOW capability, the last time that I
            #      half-heartedly tried this it din't work as expected.
            # XXX Moved here by Krister, only return actual values
            freq *= 16
            freq /= 1000

            # Lets set the freq ourselves using the V4L device.
            try:
                vd = tv.v4l2.Videodev(vg.vdev)
                print "DEBUG: in CHANNEL vdev=%s"%vg.vdev
                try:
                    vd.setfreq(freq)
                except:
                    vd.setfreq_old(freq)
                vd.close()
            except:
                print String(_('Failed to set freq for channel %s.') % chan)

        return 0


    def getChannel(self):
        return config.TV_CHANNELS[self.chan_index][2]

    def getManChannel(self,channel=0):
        return config.TV_CHANNELS[(channel-1) % len(config.TV_CHANNELS)][2]

    def getNextChannel(self):
        return config.TV_CHANNELS[(self.chan_index+1) % 
len(config.TV_CHANNELS)][2]


    def getPrevChannel(self):
        return config.TV_CHANNELS[(self.chan_index-1) % 
len(config.TV_CHANNELS)][2]


    def setChanlist(self, chanlist):
        self.chanlist = freq.CHANLIST[chanlist]


    def appSend(self, app, app_cmd):
        if not app or not app_cmd:
            return

        app.write(app_cmd)


    def getChannelInfo(self):
        '''Get program info for the current channel'''

        tuner_id = self.getChannel()
        chan_name = config.TV_CHANNELS[self.chan_index][1]
        chan_id = config.TV_CHANNELS[self.chan_index][0]

        channels = epg_xmltv.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)
        else:
            prog_info = 'No info'

        return tuner_id, chan_name, prog_info




if __name__ == '__main__':
    fc = FreevoChannels()
    print 'CHAN: %s' % fc.getChannel()
    fc.chanSet('K35', True)
    print 'CHAN: %s' % fc.getChannel()
    fc.chanSet('K35', False)
    print 'CHAN: %s' % fc.getChannel()
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# ivtv_record.py - A plugin to record tv using an ivtv based card.
# -----------------------------------------------------------------------
# $Id: ivtv_record.py 8361 2006-10-14 08:00:09Z duncan $
#
# Notes:
# Todo:        
#
# -----------------------------------------------------------------------
# Freevo - A Home Theater PC framework
# Copyright (C) 2003 Krister Lagerstrom, et al. 
# Please see the file freevo/Docs/CREDITS for a complete list of authors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------


import sys, string
import random
import time, os
import threading
import signal

import config
import tv.ivtv
import childapp 
import plugin 
import rc
import util.tv_util as tv_util

from event import Event
from tv.channels import FreevoChannels

DEBUG = config.DEBUG

CHUNKSIZE = 65536*2


class PluginInterface(plugin.Plugin):
    def __init__(self):
        plugin.Plugin.__init__(self)
        plugin.register(Recorder(), plugin.RECORD)


class Recorder:

    def __init__(self):
        # Disable this plugin if not loaded by record_server.
        if string.find(sys.argv[0], 'recordserver') == -1:
            return

        if DEBUG: print 'ACTIVATING IVTV RECORD PLUGIN'
        self.thread=[]
        for i in range(len(config.VIDEO_GROUPS)):
            #launch a thread for each capture card
            self.thread.append(Record_Thread(i))
            self.thread[-1].setDaemon(1)
            self.thread[-1].mode = 'idle'
            self.thread[-1].start()
        

    def Record(self, rec_prog):
        # It is safe to ignore config.TV_RECORDFILE_SUFFIX here.
        rec_prog.filename = 
os.path.splitext(tv_util.getProgFilename(rec_prog))[0] + '.mpeg'
        print "DEBUG: in Record, trying to record ",rec_prog.filename
        print "DEBUG: looking for an avialable card:"
        a=range(len(self.thread))
        a.reverse()
        for i in a:
            t=self.thread[i]
            if t.mode=='idle':
                print "DEBUG: found it, using card nr:",i
                t.mode = 'record'
                t.prog = rec_prog
                t.mode_flag.set()
                rec_prog.cardID=i
                return i
                break
        #what to do if I am already recording?
        if DEBUG: print('Recorder::Record: %s' % rec_prog)
        

    def Stop(self,cardID=None):
        if cardID==None:
            cardID=0
        print "DEBUG STOPPING card:",cardID
        self.thread[cardID].mode = 'stop'
        self.thread[cardID].mode_flag.set()



class Record_Thread(threading.Thread):

    def __init__(self,cardID=None):
        threading.Thread.__init__(self)
        if cardID==None: cardID=0
        self.cardID=cardID
        self.mode = 'idle'
        self.mode_flag = threading.Event()
        self.prog = None
        self.app = None


    def run(self):
        while 1:
            if DEBUG: print('Record_Thread::run: mode=%s' % self.mode)
            if self.mode == 'idle':
                self.mode_flag.wait()
                self.mode_flag.clear()
                
            elif self.mode == 'record':
                rc.post_event(Event('RECORD_START', arg=self.prog))
                if DEBUG: print 'Record_Thread::run: started recording'

                fc = FreevoChannels()
                if DEBUG: print 'CHAN: %s' % fc.getChannel()

                (v_norm, v_input, v_clist, v_dev) = config.TV_SETTINGS.split()

                #v = tv.ivtv.IVTV(v_dev)

                #v.init_settings()
                #vg = fc.getVideoGroup(self.prog.tunerid, False)
                print "----------------------------"
                print config.VIDEO_GROUPS
                print "----------------------------"
                vg =  config.VIDEO_GROUPS[self.cardID]
                print "DEBUG: cardID=",self.cardID
                v_dev=vg.vdev
                v = tv.ivtv.IVTV(v_dev)

                v.init_settings()
                if DEBUG: print 'Setting Input to %s' % vg.input_num
                v.setinput(vg.input_num)

                if DEBUG: print 'Setting Channel to %s' % self.prog.tunerid
                fc.chanSet(str(self.prog.tunerid), False,vg=vg)

                if DEBUG: v.print_settings()

                now = time.time()
                stop = now + self.prog.rec_duration

                time.sleep(2)

                v_in  = open(v_dev, 'r')
                v_out = open(self.prog.filename, 'w')

                while time.time() < stop:
                    buf = v_in.read(CHUNKSIZE)
                    v_out.write(buf)
                    if self.mode == 'stop':
                        break

                v_in.close()
                v_out.close()
                v.close()
                v = None

                self.mode = 'idle'

                rc.post_event(Event('RECORD_STOP', arg=self.prog))
                if DEBUG: print('Record_Thread::run: finished recording')

            else:
                self.mode = 'idle'

            time.sleep(0.5)
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# record_server.py - A network aware TV recording server.
# -----------------------------------------------------------------------
# $Id: recordserver.py 8398 2006-10-17 11:30:43Z duncan $
#
# -----------------------------------------------------------------------
# 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.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MER-
# CHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# -----------------------------------------------------------------------


import sys, string, random, time, os, re, pwd, stat
import config
from util import vfs

# change uid
if __name__ == '__main__':
    try:
        if config.TV_RECORD_SERVER_UID and os.getuid() == 0:
            os.setgid(config.TV_RECORD_SERVER_GID)
            os.setuid(config.TV_RECORD_SERVER_UID)
            os.environ['USER'] = pwd.getpwuid(os.getuid())[0]
            os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
    except Exception, e:
        print e

from twisted.web import xmlrpc, server
from twisted.internet.app import Application
from twisted.internet import reactor
from twisted.python import log

from util.marmalade import jellyToXML, unjellyFromXML

import rc
rc_object = rc.get_singleton(use_pylirc=0, use_netremote=0)

from tv.record_types import TYPES_VERSION
from tv.record_types import ScheduledRecordings

import tv.record_types
import tv.epg_xmltv
import util.tv_util as tv_util
import plugin
import util.popen3
from tv.channels import FreevoChannels
from util.videothumb import snapshot
from event import *

dbglvl=1

def _debug_(text, level=1):
    if config.DEBUG >= level:
        try:
            log.debug(String(text))
        except:
            log.debug('Failed to log a message')

_debug_('PLUGIN_RECORD: %s' % config.plugin_record)

appname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
logfile = '%s/%s-%s.log' % (config.LOGDIR, appname, os.getuid())
log.startLogging(open(logfile, 'a'))

plugin.init_special_plugin(config.plugin_record)

if config.TV_RECORD_PADDING_PRE == None:
    config.TV_RECORD_PADDING_PRE = config.TV_RECORD_PADDING
if config.TV_RECORD_PADDING_POST == None:
    config.TV_RECORD_PADDING_POST = config.TV_RECORD_PADDING

def print_plugin_warning():
    print '*************************************************'
    print '**  Warning: No recording plugin registered.  **'
    print '**           Check your local_conf.py for a   **'
    print '**           bad "plugin_record =" line or    **'
    print '**           this log for a plugin failure.   **'
    print '**           Recordings will fail!            **'
    print '*************************************************'


if not plugin.getbyname('RECORD'):
    print_plugin_warning()


class RecordServer(xmlrpc.XMLRPC):

    def __init__(self):
        self.fc = FreevoChannels()
        # XXX: In the future we should have one lock per VideoGroup.
        self.tv_lock_file = None
        self.vg = None


    def progsTimeCompare(self, first, second):
        t1 = first.split(':')[-1]
        t2 = second.split(':')[-1]
        try:
            return int(t1) - int(t2)
        except ArithmeticError:
            pass
        return 0

    def findNextProgram(self):
        _debug_('in findNextProgram', dbglvl+3)

        progs = self.getScheduledRecordings().getProgramList()
        now = time.time()

        next_program = None
        proglist = list(progs)
        proglist.sort(self.progsTimeCompare)
        for progitem in proglist:
            prog = progs[progitem]
            _debug_('%s' % (prog), dbglvl+1)

            try:
                recording = prog.isRecording
            except:
                recording = False
            _debug_('%s: recording=%s' % (prog.title, recording))

            if now >= prog.stop + config.TV_RECORD_PADDING_POST:
                _debug_('%s: prog.stop=%s, now=%s' % (prog.title, \
                    time.localtime(prog.stop+config.TV_RECORD_PADDING_POST), 
now), dbglvl+1)
                continue
            _debug_('%s: prog.stop=%s' % (prog.title, 
time.localtime(prog.stop)), dbglvl)

            if not recording:
                next_program = prog
                break

        self.next_program = next_program
        if next_program == None:
            _debug_('No program scheduled to record', dbglvl)
            return None

        _debug_('%s' % (next_program), dbglvl)
        return next_program


    def isPlayerRunning(self):
        '''
        returns the state of a player, mplayer, xine, etc.
        TODO:
            real player running test, check /dev/videoX.
            this could go into the upsoon client
        '''
        _debug_('in isPlayerRunning', dbglvl+3)
        return (os.path.exists(config.FREEVO_CACHEDIR + '/playing'))

    # note: add locking and r/rw options to get/save funs
    def getScheduledRecordings(self):
        file_ver = None
        scheduledRecordings = None

        if os.path.isfile(config.TV_RECORD_SCHEDULE):
            _debug_('GET: reading cached file (%s)' % config.TV_RECORD_SCHEDULE)
            if hasattr(self, 'scheduledRecordings_cache'):
                mod_time, scheduledRecordings = self.scheduledRecordings_cache
                try:
                    if os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME] == 
mod_time:
                        _debug_('Return cached data')
                        return scheduledRecordings
                except OSError:
                    pass
                
            f = open(config.TV_RECORD_SCHEDULE, 'r')
            scheduledRecordings = unjellyFromXML(f)
            f.close()
            
            try:
                file_ver = scheduledRecordings.TYPES_VERSION
            except AttributeError:
                _debug_('The cache does not have a version and must be 
recreated.')
    
            if file_ver != TYPES_VERSION:
                _debug_(('ScheduledRecordings version number %s is stale (new 
is %s), must ' +
                        'be reloaded') % (file_ver, TYPES_VERSION))
                scheduledRecordings = None
            else:
                _debug_('Got ScheduledRecordings (version %s).' % file_ver)
    
        if not scheduledRecordings:
            _debug_('GET: making a new ScheduledRecordings')
            scheduledRecordings = ScheduledRecordings()
            self.saveScheduledRecordings(scheduledRecordings)
    
        _debug_('ScheduledRecordings has %s items.' % \
                len(scheduledRecordings.programList))
    
        try:
            mod_time = os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME]
            self.scheduledRecordings_cache = mod_time, scheduledRecordings
        except OSError:
            pass
        return scheduledRecordings
    
    
    #
    # function to save the schedule to disk
    #
    def saveScheduledRecordings(self, scheduledRecordings=None):
    
        if not scheduledRecordings:
            _debug_('SAVE: making a new ScheduledRecordings')
            scheduledRecordings = ScheduledRecordings()
    
        _debug_('SAVE: saving cached file (%s)' % config.TV_RECORD_SCHEDULE)
        _debug_("SAVE: ScheduledRecordings has %s items." % \
                len(scheduledRecordings.programList))
        try:
            f = open(config.TV_RECORD_SCHEDULE, 'w')
        except IOError:
            os.unlink(config.TV_RECORD_SCHEDULE)
            f = open(config.TV_RECORD_SCHEDULE, 'w')
            
        jellyToXML(scheduledRecordings, f)
        f.close()

        try:
            mod_time = os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME]
            self.scheduledRecordings_cache = mod_time, scheduledRecordings
        except OSError:
            pass

        return TRUE

 
    def scheduleRecording(self, prog=None):
        global guide

        if not prog:
            return (FALSE, 'no prog')
    
        if prog.stop < time.time():
            return (FALSE, 'cannot record it if it is over')
            
        self.updateGuide()
    
        for chan in guide.chan_list:
            if prog.channel_id == chan.id:
                _debug_('scheduleRecording: prog.channel_id="%s" chan.id="%s" 
chan.tunerid="%s"' % (String(prog.channel_id), String(chan.id), 
String(chan.tunerid)))
                prog.tunerid = chan.tunerid
    
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addProgram(prog, tv_util.getKey(prog))
        self.saveScheduledRecordings(scheduledRecordings)

        # check, maybe we need to start right now
        self.checkToRecord()

        return (TRUE, 'recording scheduled')
    

    def removeScheduledRecording(self, prog=None):
        if not prog:
            return (FALSE, 'no prog')

        # get our version of 'prog'
        # It's a bad hack, but we can use isRecording than
        sr = self.getScheduledRecordings()
        progs = sr.getProgramList()

        for saved_prog in progs.values():
            if String(saved_prog) == String(prog):
                prog = saved_prog
                break
            
        try:
            recording = prog.isRecording
        except Exception, e:
            print e
            recording = FALSE

        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.removeProgram(prog, tv_util.getKey(prog))
        self.saveScheduledRecordings(scheduledRecordings)
        now = time.time()

        # if prog.start <= now and prog.stop >= now and recording:
        if recording:
            #print 'stopping current recording'
            rec_plugin = plugin.getbyname('RECORD')
            if rec_plugin:
                rec_plugin.Stop(prog.cardID)
       
        return (TRUE, 'recording removed')
   

    def isProgScheduled(self, prog, schedule=None):
    
        if schedule == {}:
            return (FALSE, 'prog not scheduled')

        if not schedule:
            schedule = self.getScheduledRecordings().getProgramList()

        for me in schedule.values():
            if me.start == prog.start and me.channel_id == prog.channel_id:
                return (TRUE, 'prog is scheduled')

        return (FALSE, 'prog not scheduled')


    def findProg(self, chan=None, start=None):
        global guide

        _debug_('findProg: %s, %s' % (chan, start))

        if not chan or not start:
            return (FALSE, 'no chan or no start')

        self.updateGuide()

        for ch in guide.chan_list:
            if chan == ch.id:
                _debug_('CHANNEL MATCH: %s' % ch.id)
                for prog in ch.programs:
                    if start == '%s' % prog.start:
                        _debug_('PROGRAM MATCH: %s' % prog.decode().title)
                        return (TRUE, prog.decode())

        return (FALSE, 'prog not found')


    def findMatches(self, find=None, movies_only=None):
        global guide

        _debug_('findMatches: %s' % find)
    
        matches = []
        max_results = 500

        if not find and not movies_only:
            _debug_('nothing to find')
            return (FALSE, 'no search string')

        self.updateGuide()

        pattern = '.*' + find + '\ *'
        regex = re.compile(pattern, re.IGNORECASE)
        now = time.time()

        for ch in guide.chan_list:
            for prog in ch.programs:
                if prog.stop < now:
                    continue
                if not find or regex.match(prog.title) or 
regex.match(prog.desc) \
                   or regex.match(prog.sub_title):
                    if movies_only:
                        # We can do better here than just look for the MPAA 
                        # rating.  Suggestions are welcome.
                        if 'MPAA' in prog.decode().getattr('ratings').keys():
                            matches.append(prog.decode())
                            _debug_('PROGRAM MATCH: %s' % prog.decode())
                    else:
                        # We should never get here if not find and not 
                        # movies_only.
                        matches.append(prog.decode())
                        _debug_('PROGRAM MATCH: %s' % prog.decode())
                if len(matches) >= max_results:
                    break

        _debug_('Found %d matches.' % len(matches))

        if matches:
            return (TRUE, matches)
        else:
            return (FALSE, 'no matches')


    def updateGuide(self):
        global guide

        # XXX TODO: only do this if the guide has changed?
        guide = tv.epg_xmltv.get_guide()

        
    def checkToRecord(self):
        rec_cmd = None
        rec_prog = None
        cleaned = None
        delay_recording = FALSE

        sr = self.getScheduledRecordings()
        progs = sr.getProgramList()
        currently_recording=None
        currently_recordings = []
        for prog in progs.values():
            try:
                recording = prog.isRecording
            except:
                recording = FALSE

            if recording:
                currently_recordings.append(prog)
                currently_recording=prog
        print "currently_recordings=",currently_recordings
        if len(currently_recordings)<len(config.VIDEO_GROUPS):
            #FIX THIS yes this is a hack, assuming capture devices can all 
capture the same source
            #so we are recording less channels than the number of capture 
devices, we have no problem
            currently_recording=None
            print "no conflict, cleareing currently_recording"
        now = time.time()
        for prog in progs.values():
            _debug_('checkToRecord: progloop=%s' % String(prog))

            try:
                recording = prog.isRecording
            except:
                recording = FALSE

            if (prog.start - config.TV_RECORD_PADDING_PRE) <= now \
                   and (prog.stop + config.TV_RECORD_PADDING_POST) >= now \
                   and recording == FALSE:
                # just add to the 'we want to record this' list
                # then end the loop, and figure out which has priority,
                # remember to take into account the full length of the shows
                # and how much they overlap, or chop one short
                duration = int((prog.stop + config.TV_RECORD_PADDING_POST ) - 
now - 10)
                if duration < 10:
                    return 

                if currently_recording:
                    # Hey, something is already recording!
                    if prog.start - 10 <= now:
                        # our new recording should start no later than now!
                        # check if the new prog is a favorite and the current 
running is
                        # not. If so, the user manually added something, we 
guess it
                        # has a higher priority.
                        if self.isProgAFavorite(prog)[0] and \
                           not self.isProgAFavorite(currently_recording)[0] and 
\
                           prog.stop + config.TV_RECORD_PADDING_POST > now:
                            _debug_('ignore %s' % String(prog))
                            continue
                        sr.removeProgram(currently_recording, 
                                         tv_util.getKey(currently_recording))
                        plugin.getbyname('RECORD').Stop(prog.cardID)
                        time.sleep(5)
                        _debug_('CALLED RECORD STOP 1')
                    else:
                        # at this moment we must be in the pre-record padding
                        if currently_recording.stop - 10 <= now:
                            # The only reason we are still recording is because 
of
                            # the post-record padding.
                            # Therefore we have overlapping paddings but not
                            # real stop / start times.
                            overlap = (currently_recording.stop + \
                                       config.TV_RECORD_PADDING_POST) - \
                                      (prog.start - 
config.TV_RECORD_PADDING_PRE)
                            if overlap <= ((config.TV_RECORD_PADDING_PRE +
                                            config.TV_RECORD_PADDING_POST)/4):
                                sr.removeProgram(currently_recording, 
                                                 
tv_util.getKey(currently_recording))
                                plugin.getbyname('RECORD').Stop(prog.cardID)
                                time.sleep(5)
                                _debug_('CALLED RECORD STOP 2')
                            else: 
                                delay_recording = TRUE
                        else: 
                            delay_recording = TRUE
                             
                        
                if delay_recording:
                    _debug_('delaying: %s' % String(prog))
                else:
                    _debug_('going to record: %s' % String(prog))
                    prog.isRecording = TRUE
                    prog.rec_duration = duration
                    prog.filename = tv_util.getProgFilename(prog)
                    rec_prog = prog


        for prog in progs.values():
            # If the program is over remove the entry.
            if ( prog.stop + config.TV_RECORD_PADDING_POST) < now:
                _debug_('found a program to clean')
                cleaned = TRUE
                del progs[tv_util.getKey(prog)]

        if rec_prog or cleaned:
            sr.setProgramList(progs)
            self.saveScheduledRecordings(sr)

        if rec_prog:
            _debug_('start recording')
            self.record_app = plugin.getbyname('RECORD')

            if not self.record_app:
                print_plugin_warning()
                print 'ERROR:  Recording %s failed.' % String(rec_prog.title)
                self.removeScheduledRecording(rec_prog)
                return

            self.vg = self.fc.getVideoGroup(rec_prog.channel_id, False)
            rec_prog.cardID=self.record_app.Record(rec_prog)
            #yes there is a small chance that you get a problem. by not 
creating the lockfile before starting the record
            suffix="%s"%rec_prog.cardID
            rec_prog.tv_lock_file = config.FREEVO_CACHEDIR + '/record.'+suffix
            


    def addFavorite(self, name, prog, exactchan=FALSE, exactdow=FALSE, 
exacttod=FALSE):
        if not name:
            return (FALSE, 'no name')
    
        (status, favs) = self.getFavorites()
        priority = len(favs) + 1
        fav = tv.record_types.Favorite(name, prog, exactchan, exactdow, 
exacttod, priority)
    
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addFavorite(fav)
        self.saveScheduledRecordings(scheduledRecordings)
        self.addFavoriteToSchedule(fav)

        return (TRUE, 'favorite added')
    
    
    def addEditedFavorite(self, name, title, chan, dow, mod, priority):
        fav = tv.record_types.Favorite()
    
        fav.name = name
        fav.title = title
        fav.channel = chan
        fav.dow = dow
        fav.mod = mod
        fav.priority = priority
    
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.addFavorite(fav)
        self.saveScheduledRecordings(scheduledRecordings)
        self.addFavoriteToSchedule(fav)

        return (TRUE, 'favorite added')
    
    
    def removeFavorite(self, name=None):
        if not name:
            return (FALSE, 'no name')
       
        (status, fav) = self.getFavorite(name)
        self.removeFavoriteFromSchedule(fav)
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.removeFavorite(name)
        self.saveScheduledRecordings(scheduledRecordings)

        return (TRUE, 'favorite removed')
       
    
    def clearFavorites(self):
        scheduledRecordings = self.getScheduledRecordings()
        scheduledRecordings.clearFavorites()
        self.saveScheduledRecordings(scheduledRecordings)

        return (TRUE, 'favorites cleared')
    
    
    def getFavorites(self):
        return (TRUE, self.getScheduledRecordings().getFavorites())
    
    
    def getFavorite(self, name):
        (status, favs) = self.getFavorites()
    
        if favs.has_key(name):
            fav = favs[name] 
            return (TRUE, fav)
        else:
            return (FALSE, 'not a favorite')
    
    
    def adjustPriority(self, favname, mod=0):
        save = []
        mod = int(mod)
        (status, me) = self.getFavorite(favname)
        oldprio = int(me.priority)
        newprio = oldprio + mod
    
        _debug_('ap: mod=%s\n' % mod)
       
        sr = self.getScheduledRecordings()
        favs = sr.getFavorites().values()
    
        sys.stderr.write('adjusting prio of '+favname+'\n')
        for fav in favs:
            fav.priority = int(fav.priority)
    
            if fav.name == me.name:
                _debug_('MATCH')
                fav.priority = newprio
                _debug_('moved prio of %s: %s => %s\n' % (fav.name, oldprio, 
newprio))
                continue
            if mod < 0:
                if fav.priority < newprio or fav.priority > oldprio:
                    _debug_('fp: %s, old: %s, new: %s\n' % (fav.priority, 
oldprio, newprio))
                    _debug_('skipping: %s\n' % fav.name)
                    continue
                fav.priority = fav.priority + 1
                _debug_('moved prio of %s: %s => %s\n' % (fav.name, 
fav.priority-1, fav.priority))
                
            if mod > 0:
                if fav.priority > newprio or fav.priority < oldprio:
                    _debug_('skipping: %s\n' % fav.name)
                    continue
                fav.priority = fav.priority - 1
                _debug_('moved prio of %s: %s => %s\n' % (fav.name, 
fav.priority+1, fav.priority))
    
        sr.setFavoritesList(favs)
        self.saveScheduledRecordings(sr)

        return (TRUE, 'priorities adjusted')
    
    
    def isProgAFavorite(self, prog, favs=None):
        if not favs:
            (status, favs) = self.getFavorites()
    
        lt = time.localtime(prog.start)
        dow = '%s' % lt[6]
        # tod = '%s:%s' % (lt[3], lt[4])
        # mins_in_day = 1440
        min_of_day = '%s' % ((lt[3]*60)+lt[4])
    
        for fav in favs.values():
            if 
prog.title.encode('utf-8').lower().find(fav.title.encode('utf-8').lower()) >= 0:
                if fav.channel == tv_util.get_chan_displayname(prog.channel_id) 
\
                   or fav.channel == 'ANY':
                    if Unicode(fav.dow) == Unicode(dow) or Unicode(fav.dow) == 
u'ANY':
                        if Unicode(fav.mod) == Unicode(min_of_day) or \
                               Unicode(fav.mod) == u'ANY':
                            return (TRUE, fav.name)
                        elif abs(int(fav.mod) - int(min_of_day)) <= 8:
                            return (TRUE, fav.name)
    
        # if we get this far prog is not a favorite
        return (FALSE, 'not a favorite')
    
    
    def removeFavoriteFromSchedule(self, fav):
        # TODO: make sure the program we remove is not
        #       covered by another favorite.
    
        tmp = {}
        tmp[fav.name] = fav
    
        scheduledRecordings = self.getScheduledRecordings()
        progs = scheduledRecordings.getProgramList()
        for prog in progs.values():
            (isFav, favorite) = self.isProgAFavorite(prog, tmp)
            if isFav:
                self.removeScheduledRecording(prog)

        return (TRUE, 'favorite unscheduled')
    
    
    def addFavoriteToSchedule(self, fav):
        global guide
        favs = {}
        favs[fav.name] = fav

        self.updateGuide()
    
        for ch in guide.chan_list:
            for prog in ch.programs:
                (isFav, favorite) = self.isProgAFavorite(prog, favs)
                if isFav:
                    prog.isFavorite = favorite
                    self.scheduleRecording(prog)

        return (TRUE, 'favorite scheduled')
    
    
    def updateFavoritesSchedule(self):
        #  TODO: do not re-add a prog to record if we have
        #        previously decided not to record it.

        global guide
    
        self.updateGuide()
    
        # First get the timeframe of the guide.
        last = 0
        for ch in guide.chan_list:
            for prog in ch.programs:
                if prog.start > last: last = prog.start
    
        scheduledRecordings = self.getScheduledRecordings()
    
        (status, favs) = self.getFavorites()

        if not len(favs):
            return (FALSE, 'there are no favorites to update')
       
    
        # Then remove all scheduled favorites in that timeframe to
        # make up for schedule changes.
        progs = scheduledRecordings.getProgramList()
        for prog in progs.values():
    
            # try:
            #     favorite = prog.isFavorite
            # except:
            #     favorite = FALSE
    
            # if prog.start <= last and favorite:
            (isFav, favorite) = self.isProgAFavorite(prog, favs)
            if prog.start <= last and isFav:
                # do not yet remove programs currently being recorded:
                isRec = hasattr(prog, "isRecording") and prog.isRecording
                if not isRec:
                    self.removeScheduledRecording(prog)
    
        for ch in guide.chan_list:
            for prog in ch.programs:
                (isFav, favorite) = self.isProgAFavorite(prog, favs)
                isRec = hasattr(prog, "isRecording") and prog.isRecording
                if isFav and not isRec:
                    prog.isFavorite = favorite
                    self.scheduleRecording(prog)

        return (TRUE, 'favorites schedule updated')
    

    #################################################################
    #  Start XML-RPC published methods.                             #
    #################################################################

    def xmlrpc_isPlayerRunning(self):
        status = self.isPlayerRunning()
        message = status and 'player is running' or 'player is not running'
        return (status, message)

    def xmlrpc_findNextProgram(self):
        response = self.findNextProgram()
        status = response != None
        return (status, jellyToXML(response))

    def xmlrpc_getScheduledRecordings(self):
        return (TRUE, jellyToXML(self.getScheduledRecordings()))


    def xmlrpc_saveScheduledRecordings(self, scheduledRecordings=None):
        status = self.saveScheduledRecordings(scheduledRecordings)

        if status:
            return (status, 'saveScheduledRecordings::success')
        else:
            return (status, 'saveScheduledRecordings::failure')


    def xmlrpc_scheduleRecording(self, prog=None):
        if not prog:
            return (FALSE, 'RecordServer::scheduleRecording:  no prog')

        prog = unjellyFromXML(prog)

        (status, response) = self.scheduleRecording(prog)

        return (status, 'RecordServer::scheduleRecording: %s' % response)


    def xmlrpc_removeScheduledRecording(self, prog=None):
        if not prog:
            return (FALSE, 'RecordServer::removeScheduledRecording:  no prog')

        prog = unjellyFromXML(prog)

        (status, response) = self.removeScheduledRecording(prog)

        return (status, 'RecordServer::removeScheduledRecording: %s' % response)


    def xmlrpc_isProgScheduled(self, prog=None, schedule=None):
        if not prog:
            return (FALSE, 'removeScheduledRecording::failure:  no prog')

        prog = unjellyFromXML(prog)

        if schedule:
            schedule = unjellyFromXML(schedule)

        (status, response) = self.isProgScheduled(prog, schedule)

        return (status, 'RecordServer::isProgScheduled: %s' % response)


    def xmlrpc_findProg(self, chan, start):
        (status, response) = self.findProg(chan, start)

        if status:
            return (status, jellyToXML(response))
        else:
            return (status, 'RecordServer::findProg: %s' % response)


    def xmlrpc_findMatches(self, find, movies_only):
        (status, response) = self.findMatches(find, movies_only)

        if status:
            return (status, jellyToXML(response))
        else:
            return (status, 'RecordServer::findMatches: %s' % response)


    def xmlrpc_echotest(self, blah):
        return (TRUE, 'RecordServer::echotest: %s' % blah)


    def xmlrpc_addFavorite(self, name, prog, exactchan=FALSE, exactdow=FALSE, 
exacttod=FALSE):
        prog = unjellyFromXML(prog)
        (status, response) = self.addFavorite(name, prog, exactchan, exactdow, 
exacttod)

        return (status, 'RecordServer::addFavorite: %s' % response)


    def xmlrpc_addEditedFavorite(self, name, title, chan, dow, mod, priority):
        (status, response) = \
            self.addEditedFavorite(unjellyFromXML(name), \
            unjellyFromXML(title), chan, dow, mod, priority)

        return (status, 'RecordServer::addEditedFavorite: %s' % response)


    def xmlrpc_removeFavorite(self, name=None):
        (status, response) = self.removeFavorite(name)

        return (status, 'RecordServer::removeFavorite: %s' % response)


    def xmlrpc_clearFavorites(self):
        (status, response) = self.clearFavorites()

        return (status, 'RecordServer::clearFavorites: %s' % response)


    def xmlrpc_getFavorites(self):
        return (TRUE, jellyToXML(self.getScheduledRecordings().getFavorites()))


    def xmlrpc_getFavorite(self, name):
        (status, response) = self.getFavorite(name)

        if status:
            return (status, jellyToXML(response))
        else:
            return (status, 'RecordServer::getFavorite: %s' % response)


    def xmlrpc_adjustPriority(self, favname, mod=0):
        (status, response) = self.adjustPriority(favname, mod)

        return (status, 'RecordServer::adjustPriority: %s' % response)


    def xmlrpc_isProgAFavorite(self, prog, favs=None):
        prog = unjellyFromXML(prog)
        if favs:
            favs = unjellyFromXML(favs)

        (status, response) = self.isProgAFavorite(prog, favs)

        return (status, 'RecordServer::adjustPriority: %s' % response)


    def xmlrpc_removeFavoriteFromSchedule(self, fav):
        (status, response) = self.removeFavoriteFromSchedule(fav)

        return (status, 'RecordServer::removeFavoriteFromSchedule: %s' % 
response)


    def xmlrpc_addFavoriteToSchedule(self, fav):
        (status, response) = self.addFavoriteToSchedule(fav)

        return (status, 'RecordServer::addFavoriteToSchedule: %s' % response)


    def xmlrpc_updateFavoritesSchedule(self):
        (status, response) = self.updateFavoritesSchedule()

        return (status, 'RecordServer::updateFavoritesSchedule: %s' % response)


    #################################################################
    #  End XML-RPC published methods.                               #
    #################################################################


    def create_fxd(self, rec_prog):
        from util.fxdimdb import FxdImdb, makeVideo
        fxd = FxdImdb()

        (filebase, fileext) = os.path.splitext(rec_prog.filename)
        fxd.setFxdFile(filebase, overwrite = True)

        video = makeVideo('file', 'f1', os.path.basename(rec_prog.filename))
        fxd.setVideo(video)
        fxd.info['tagline'] = fxd.str2XML(rec_prog.sub_title)
        fxd.info['plot'] = fxd.str2XML(rec_prog.desc)
        fxd.info['runtime'] = None
        fxd.info['recording_timestamp'] = str(time.time())
        fxd.info['year'] = time.strftime('%m-%d ' + config.TV_TIMEFORMAT, 
                                         time.localtime(rec_prog.start))
        fxd.title = rec_prog.title 
        fxd.writeFxd()
            

    def startMinuteCheck(self):
        next_minute = (int(time.time()/60) * 60 + 60) - int(time.time())
        _debug_('top of the minute in %s seconds' % next_minute)
        reactor.callLater(next_minute, self.minuteCheck)
        
    def minuteCheck(self):
        next_minute = (int(time.time()/60) * 60 + 60) - int(time.time())
        if next_minute != 60:
            # Compensate for timer drift 
            if config.DEBUG:
                log.debug('top of the minute in %s seconds' % next_minute)
            reactor.callLater(next_minute, self.minuteCheck)
        else:
            reactor.callLater(60, self.minuteCheck)

        self.checkToRecord()


    def eventNotice(self):
        #print 'RECORDSERVER GOT EVENT NOTICE'
        # Use callLater so that handleEvents will get called the next time
        # through the main loop.
        reactor.callLater(0, self.handleEvents) 


    def handleEvents(self):
        #print 'RECORDSERVER HANDLING EVENT'
        event = rc_object.get_event()

        if event:
            if event == OS_EVENT_POPEN2:
                print 'popen %s' % event.arg[1]
                event.arg[0].child = util.popen3.Popen3(event.arg[1])

            elif event == OS_EVENT_WAITPID:
                pid = event.arg[0]
                print 'waiting on pid %s' % pid

                for i in range(20):
                    try:
                        wpid = os.waitpid(pid, os.WNOHANG)[0]
                    except OSError:
                        # forget it
                        continue
                    if wpid == pid:
                        break
                    time.sleep(0.1)

            elif event == OS_EVENT_KILL:
                pid = event.arg[0]
                sig = event.arg[1]

                print 'killing pid %s with sig %s' % (pid, sig)
                try:
                    os.kill(pid, sig)
                except OSError:
                    pass

                for i in range(20):
                    try:
                        wpid = os.waitpid(pid, os.WNOHANG)[0]
                    except OSError:
                        # forget it
                        continue
                    if wpid == pid:
                        break
                    time.sleep(0.1)

                else:
                    print 'force killing with signal 9'
                    try:
                        os.kill(pid, 9)
                    except OSError:
                        pass
                    for i in range(20):
                        try:
                            wpid = os.waitpid(pid, os.WNOHANG)[0]
                        except OSError:
                            # forget it
                            continue
                        if wpid == pid:
                            break
                        time.sleep(0.1)
                print 'recorderver: After wait()'

            elif event == RECORD_START:
                #print 'Handling event RECORD_START'
                prog = event.arg
                open(prog.tv_lock_file, 'w').close()
                self.create_fxd(prog)
                if config.VCR_PRE_REC:
                    util.popen3.Popen3(config.VCR_PRE_REC)

            elif event == RECORD_STOP:
                #print 'Handling event RECORD_STOP'
                prog = event.arg
                os.remove(prog.tv_lock_file)
                try:
                    snapshot(prog.filename)
                except:
                    # If automatic pickling fails, use on-demand caching when
                    # the file is accessed instead. 
                    os.rename(vfs.getoverlay(prog.filename + '.raw.tmp'),
                              vfs.getoverlay(os.path.splitext(prog.filename)[0] 
+ '.png'))
                    pass
                if config.VCR_POST_REC:
                    util.popen3.Popen3(config.VCR_POST_REC)

            else:
                print 'not handling event %s' % str(event)
                return
        else:
            print 'no event to get' 


def main():
    app = Application("RecordServer")
    rs = RecordServer()
    if (config.DEBUG == 0):
        app.listenTCP(config.TV_RECORD_SERVER_PORT, server.Site(rs, 
logPath='/dev/null'))
    else:
        app.listenTCP(config.TV_RECORD_SERVER_PORT, server.Site(rs))
    rs.startMinuteCheck()
    rc_object.subscribe(rs.eventNotice)
    app.run(save=0)
    

if __name__ == '__main__':
    import traceback
    import time
    import glob

    locks = glob.glob(config.FREEVO_CACHEDIR + '/record.*')
    for f in locks:
        print 'Removed old record lock \"%s\"' % f
        os.remove(f)

    while 1:
        try:
            start = time.time()
            main()
            break
        except:
            traceback.print_exc()
            if start + 10 > time.time():
                print 'server problem, sleeping 1 min'
                time.sleep(60)

-------------------------------------------------------------------------
Using Tomcat but need to do more? Need to support web services, security?
Get stuff done quickly with pre-integrated technology to make your job easier
Download IBM WebSphere Application Server v.1.0.1 based on Apache Geronimo
http://sel.as-us.falkag.net/sel?cmd=lnk&kid=120709&bid=263057&dat=121642
_______________________________________________
Freevo-users mailing list
Freevo-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/freevo-users

Reply via email to