Re: anyone interested in multirecoding patches?

Paul Sijben <[email protected]> Sat, 24 Dec 2011 08:31:14 +0100
Newsgroups gmane.comp.video.freevo.user
Message-ID <[email protected]>
Hi Adam,

I believe these are all the files I edited. These are based on the files
from 1.9.0 so I give you the full files rather than a diff.

The change in videothumb.py is rather subtile, it ensures that when two
shows end the recording server does not freeze indefinitely. However the
change does not prevent sometimes a  thumbnail to end up attached to the
wrong recording. It has not irritated me enough to fix that ;-)

Your local_conf.py should have something like this:

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

   ]

hope you will find it useful

Paul

On 22-12-11 15:19, Adam Charrett wrote:
>
> On Wed, 21 Dec 2011, Paul Sijben wrote:
>
>> Hi all,
>>
>> I had to reinstall my freevo box and had to reapply my patches so I
>> could record from multiple ivtv cards concurrently.
>>
>> Is anyone interested in those patches?
>>
> Yes, please send them to me. I'm in the process of working out a 1.9/2 TV 
> server and I expect I could draw some inspiration from these.
>
> Thanks
>
> Adam
>
> ------------------------------------------------------------------------------
> Write once. Port to many.
> Get the SDK and tools to simplify cross-platform app development. Create 
> new or port existing apps to sell to consumers worldwide. Explore the 
> Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join
> http://p.sf.net/sfu/intel-appdev
> _______________________________________________
> Freevo-users mailing list
> [email protected]
> https://lists.sourceforge.net/lists/listinfo/freevo-users

-- 
Paul Sijben
tel 0334557522

------------------------------------------------------------------------------
Write once. Port to many.
Get the SDK and tools to simplify cross-platform app development. Create 
new or port existing apps to sell to consumers worldwide. Explore the 
Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join
http://p.sf.net/sfu/intel-appdev

_______________________________________________
Freevo-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/freevo-users
channels.py (text/plain, 14.6 KB)
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# Freevo module to handle channel changing.
# -----------------------------------------------------------------------
# $Id: channels.py 11479 2009-05-07 17:34:38Z 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 threading
import time
import os

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

CHANNEL_ID = 0
DISPLAY_NAME = 1
TUNER_ID = 2


# Sample for local_conf.py:
# Three video cards and one web camera.
#TV_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):
        _debug_('FreevoChannels.__init__()', 2)
        self.chan_index = 0
        self.lock = threading.Lock()

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


    def _findVideoGroup(self, chan, isplayer, chan_index):
        """
        Find the VideoGroup number used by this Freevo channel.
        """
        group = 0
        for i in range(len(config.TV_CHANNELS)):
            chan_info = config.TV_CHANNELS[i]
            if chan_info[chan_index] == chan:
                try:
                    group = int(chan_info[4])
                except (IndexError, ValueError):
                    pass
                break
        else: # Channel not found
            #DJW this should be noticed
            import traceback
            traceback.print_stack()

        return group


    def getVideoGroup(self, chan, isplayer, chan_index=TUNER_ID):
        """
        Gets the VideoGroup object used by this Freevo channel.
        """
        _debug_('getVideoGroup(chan=%r, isplayer=%r, chan_index=%r)' % (chan, isplayer, chan_index), 1)

        vg = config.TV_VIDEO_GROUPS[0]

        self.lock.acquire()
        try:
            try:
                channel = int(chan)
                if channel <= 0: # channels start at 1
                    group = -channel
                else:
                    group = self._findVideoGroup(chan, isplayer, chan_index)
            except ValueError:
                group = self._findVideoGroup(chan, isplayer, chan_index)

            vg = config.TV_VIDEO_GROUPS[group]
            if not isplayer:
                record_group = vg.record_group
                if record_group:
                    try:
                        # some simple checks
                        vg = config.TV_VIDEO_GROUPS[int(record_group)]
                    except:
                        _debug_('TV_VIDEO_GROUPS[%s].record_group=%s is invalid' % (group, record_group), DWARNING)
        finally:
            self.lock.release()

        return vg


    def chanUp(self, isplayer, app=None, app_cmd=None):
        """
        Using this method will not support custom frequencies.
        """
        _debug_('chanUp(isplayer=%r, app=%r, app_cmd=%r)' % (isplayer, app, app_cmd), 2)
        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.
        """
        _debug_('chanDown(isplayer=%r, app=%r, app_cmd=%r)' % (isplayer, app, app_cmd), 2)
        return self.chanSet(self.getPrevChannel(), isplayer, app, app_cmd)


    def chanSet(self, chan, isplayer, app=None, app_cmd=None, vg=None):
        _debug_('chanSet(char=%r, isplayer=%r, app=%r, app_cmd=%r)' % (chan, isplayer, app, app_cmd), 2)
        new_chan = None

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

        if not new_chan:
            _debug_('Cannot find tuner channel "%s" in the TV channel listing' % chan, DWARNING)
            return

        # Trying to choose the correct TV_CHANNELS if the channel is in more than one line
        if len(dup_dict) > 1 and new_chan >= 0:
            # Normally, negative channels are for external tuners, don't care ... yet !
            cwday = time.localtime()[6] + 1
            ctime = str(time.localtime()[3]) + str(time.localtime()[4])
            # check if some lines can be removed from dup_dict
            for key in dup_dict.keys():
                swday  = '1234567'
                sstime = '0000'
                setime = '2359'
                if len(dup_dict[key]) > 3:
                    cal = dup_dict[key][3]
                    if len(cal) > 0:
                        swday = cal[0]
                    if len(cal) > 1:
                        sstime = cal[1]
                    if len(cal) > 2:
                        setime = cal[2]

                if swday.count(str(cwday)) == 0: # current day is not in the schedule
                    del dup_dict[key]
                else:
                    if (sstime > setime and (ctime > setime and ctime < sstime)) \
                    or (sstime < setime and (ctime < sstime or ctime > setime)):
                        # current time not in this schedule
                        del dup_dict[key]

            if len(dup_dict) > 1:
                _debug_('At current day/time (%s, %s), still %s active TV_Channels for channel %s' % \
                    (cwday, ctime, len(dup_dict), chan), DWARNING)

            for key in dup_dict.keys():
                self.chan_index = int(key)

        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
        return self.tunerSetFreq(chan, isplayer, app, app_cmd, vg)


    def tunerSetFreq(self, chan, isplayer, app=None, app_cmd=None,vg=None):
        _debug_('tunerSetFreq(chan=%r, isplayer=%r, app=%r, app_cmd=%r' % (chan, isplayer, app, app_cmd), 2)
        chan = str(chan)
        if not vg: vg = self.getVideoGroup(chan, isplayer)

        freq = config.TV_FREQUENCY_TABLE.get(chan)
        if freq:
            _debug_('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':
                    _debug_('Unable to get channel list for %s.' % vg.tuner_chanlist, DWARNING)
                return 0
            if not freq:
                if vg.group_type != 'dvb':
                    _debug_('Unable to get channel list for %s.' % vg.tuner_chanlist, DWARNING)
                return 0
            _debug_('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)

                try:
                    vd.setinputbyname(vg.input_type)
                except KeyError:
                    _debug_('Cannot set input %r for %r, must be one of:\n%r' % \
                        (vg.input_type, vg.vdev, vd.inputs.keys()), DWARNING)

                try:
                    vd.setstdbyname(vg.tuner_norm)
                except KeyError:
                    _debug_('Cannot set standard %r for %r, must be one of:\n%r' % \
                        (vg.tuner_norm, vg.vdev, vd.standards.keys()), DWARNING)

                try:
                    vd.setfreq(freq)
                except:
                    vd.setfreq_old(freq)

                vd.close()
            except Exception, e:
                _debug_('Cannot set frequency for %s/%s/%s: %s' % (vg.input_type, vg.tuner_norm, chan, e), DWARNING)

            if vg.cmd:
                _debug_('run cmd: %s' % vg.cmd)
                retcode = os.system(vg.cmd)
                _debug_('exit code: %g' % retcode)

        return 0


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


    def getChannelNum(self):
        _debug_('getChannelNum()', 2)
        return (self.chan_index) % len(config.TV_CHANNELS)


    def getManChannelNum(self, channel=0, tvlike=0):
        _debug_('getManChannelNum(channel=%r)' % (channel,), 2)
        if tvlike:
            physchan = '9999'
            key = channel
            for pos in range(len(config.TV_CHANNELS)):
                if pos == channel -1:
                    key = channel
                if config.TV_CHANNELS[pos][2] == physchan:
                    channel = channel + 1
                physchan = config.TV_CHANNELS[pos][2]
            if key > len(config.TV_CHANNELS) + 1:
                key = len(config.TV_CHANNELS) + 1
            return key-1
        else:
            return (channel-1) % len(config.TV_CHANNELS)

    def getNextChannelNum(self):
        _debug_('getNextChannelNum()', 2)
        curnum=self.chan_index
        curchan=config.TV_CHANNELS[curnum][2]
        while config.TV_CHANNELS[curnum][2] == curchan:
            curnum = (curnum + 1) % len(config.TV_CHANNELS)
        return curnum


    def getPrevChannelNum(self):
        _debug_('getPrevChannelNum()', 2)
        curnum=self.chan_index
        curchan=config.TV_CHANNELS[curnum][2]
        while config.TV_CHANNELS[curnum][2] == curchan:
            curnum = (curnum - 1) % len(config.TV_CHANNELS)
        return curnum


    def getManChannel(self, channel=0, tvlike=0):
        _debug_('getManChannel(channel=%r)' % (channel,), 2)
        return config.TV_CHANNELS[self.getManChannelNum(channel, tvlike)][2]


    def getNextChannel(self):
        _debug_('getNextChannel()', 2)
        return config.TV_CHANNELS[self.getNextChannelNum()][2]


    def getPrevChannel(self):
        _debug_('getPrevChannel()', 2)
        return config.TV_CHANNELS[self.getPrevChannelNum()][2]


    def setChanlist(self, chanlist):
        _debug_('setChanlist(chanlist=%r)' % (chanlist,), 2)
        self.chanlist = freq.CHANLIST[chanlist]


    def appSend(self, app, app_cmd):
        _debug_('appSend(app=%r, app_cmd=%r)' % (app, app_cmd), 2)
        if not app or not app_cmd:
            return
        app.write(app_cmd)


    def getChannelInfo(self, showtime=True):
        """Get program info for the current channel"""
        _debug_('getChannelInfo(showtime=%r)' % (showtime,), 2)

        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().get_programs(time.time(), time.time(), chan_id)

        if channels and channels[0] and channels[0].programs:
            if showtime:
                start_s = time.strftime(config.TV_TIME_FORMAT, time.localtime(channels[0].programs[0].start))
                stop_s = time.strftime(config.TV_TIME_FORMAT, 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 = channels[0].programs[0].title
        else:
            prog_info = 'No info'

        return tuner_id, chan_name, prog_info


    def getChannelInfoRaw(self):
        """Get program info for the current channel"""
        _debug_('getChannelInfoRaw()', 2)

        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().get_programs(time.time(), time.time(), chan_id)

        if channels and channels[0] and channels[0].programs:
            start_t = channels[0].programs[0].start
            stop_t = channels[0].programs[0].stop
            prog_s = channels[0].programs[0].title
        else:
            start_t = 0
            stop_t = 0
            prog_s = 'No info'

        return tuner_id, chan_id, chan_name, start_t, stop_t, prog_s


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()
ivtv_record.py (text/plain, 9.3 KB)
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# A plugin to record tv using an ivtv based card.
# -----------------------------------------------------------------------
# $Id: ivtv_record.py 11249 2009-01-14 19:01:10Z 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 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
import tv.v4l2 as V4L2


CHUNKSIZE = 65536


class PluginInterface(plugin.Plugin):
    """
    A plugin to record tv using an ivtv based card
    """

    def __init__(self):
        _debug_('PluginInterface.__init__()', 2)
        plugin.Plugin.__init__(self)
        plugin.register(Recorder(), plugin.RECORD)


class Recorder:

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

        _debug_('ACTIVATING IVTV RECORD PLUGIN', DINFO)
        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):
        _debug_('Record(rec_prog=%r)' % (rec_prog), 2)
        # It is safe to ignore config.TV_RECORD_FILE_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
                vdev=config.VIDEO_GROUPS[i].vdev
                return i,vdev
                break
        #what to do if I am already recording?
        _debug_('Recorder::Record: %s' % rec_prog, DINFO)


    def Stop(self,cardID=None):
        _debug_('Stop(%s)'%cardID, DINFO)
        if cardID==None:
            cardID=0
        self.thread[cardID].mode = 'stop'
        self.thread[cardID].mode_flag.set()


class Record_Thread(threading.Thread):

    def __init__(self,cardID=None):
        _debug_('Record_Thread.__init__()', 2)
        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):
        _debug_('Record_Thread.run()', 2)
        while 1:
            _debug_('Record_Thread::run: mode=%s' % self.mode)
            if self.mode == 'idle':
                self.mode_flag.wait()
                self.mode_flag.clear()

            elif self.mode == 'record':
                try:
                    _debug_('Record_Thread::run: started recording', DINFO)

                    fc = FreevoChannels()
                    _debug_('Channel: %s' % (fc.getChannel()))
                    print "----------------------------"
                    print config.VIDEO_GROUPS
                    print "----------------------------"
                    vg =  config.VIDEO_GROUPS[self.cardID]
                    print "DEBUG: cardID=",self.cardID
                    v_dev=vg.vdev
                    #vg = fc.getVideoGroup(self.prog.tunerid, False)

                    _debug_('Opening device %r' % (vg.vdev))
                    v = tv.ivtv.IVTV(vg.vdev)

                    v.init_settings()

                    _debug_('Setting input to %r' % (vg.input_type))
                    v.setinput(vg.input_num)

                    cur_std = v.getstd()
                    try:
                        new_std = V4L2.NORMS.get(vg.tuner_norm)
                        if cur_std != new_std:
                            _debug_('Setting standard to %s' % (new_std))
                            v.setstd(new_std)
                    except:
                        _debug_("Videogroup norm value '%s' not from NORMS: %s" % \
                                (vg.tuner_norm, V4L2.NORMS.keys()), DERROR)

                    _debug_('Setting channel to %r' % self.prog.tunerid)
                    fc.chanSet(str(self.prog.tunerid), False,vg=vg)

                    # Setting the input sound level on the PVR card
                    channel = self.prog.tunerid
                    try:
                        # lookup the channel name in TV_CHANNELS
                        for pos in range(len(config.TV_CHANNELS)):
                            entry = config.TV_CHANNELS[pos]
                            if str(channel) == str(entry[2]):
                                channel_index = pos
                                break
                    except ValueError:
                        pass

                    _debug_('SetAudioByChannel: Channel: %r TV_CHANNEL pos(%d)' % (channel, channel_index))
                    try:
                        ivtv_avol = vg.avol
                    except AttributeError:
                        ivtv_avol = 0
                    if ivtv_avol <= 0:
                        _debug_('SetAudioByChannel: The tv_video group for %r doesn\'t set the volume' % channel)
                    else:
                        # Is there a specific volume level in TV_CHANNELS_VOLUME
                        avol_percent = 100
                        try:
                            # lookup the channel name in TV_CHANNELS
                            for pos in range(len(config.TV_CHANNELS_VOLUME)):
                                if config.TV_CHANNELS_VOLUME[pos][0] == config.TV_CHANNELS[channel_index][0]:
                                    avol_percent = config.TV_CHANNELS_VOLUME[pos][1]
                                    break
                        except:
                            pass

                        try:
                            avol_percent = int(avol_percent)
                        except ValueError:
                            avol_percent = 100

                        avol = int(ivtv_avol * avol_percent / 100)
                        if avol > 65535:
                            avol = 65535
                        if avol < 0:
                            avol = 0
                        _debug_('SetAudioByChannel: Current PVR Sound level is : %s' % v.getctrl(0x00980905))
                        _debug_('SetAudioByChannel: Set the PVR Sound Level to : %s (%s * %s)' % (avol, ivtv_avol, avol_percent))
                        v.setctrl(0x00980905, avol)
                        _debug_('SetAudioByChannel: New PVR Sound level is : %s' % v.getctrl(0x00980905))

                    if vg.cmd != None:
                        _debug_("Running command %r" % vg.cmd)
                        retcode = os.system(vg.cmd)
                        _debug_("exit code: %g" % retcode)

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

                    rc.post_event(Event('RECORD_START', arg=self.prog))
                    time.sleep(2)

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

                    _debug_('Recording from %r to %r in %s byte chunks' % (vg.vdev, self.prog.filename, CHUNKSIZE))
                    while time.time() < stop:
                        buf = v_in.read(CHUNKSIZE)
                        v_out.write(buf)
                        if self.mode in [ 'stop' , 'idle']:
                            _debug_('Recording stopped', DINFO)
                            break

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

                    self.mode = 'idle'

                    rc.post_event(Event('RECORD_STOP', arg=self.prog))
                    _debug_('Record_Thread::run: finished recording', DINFO)
                except Exception, why:
                    _debug_('%s' % (why), DCRITICAL)
                    return

            else:
                self.mode = 'idle'

            time.sleep(0.5)
recordserver.py (text/plain, 60.9 KB)
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# A network aware TV recording server.
# -----------------------------------------------------------------------
# $Id: recordserver.py 11543 2009-05-23 12:51:25Z 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, threading, hashlib, datetime, copy
from threading import Thread
try:
    import cPickle as pickle
except ImportError:
    import pickle
import logging
import __builtin__

import config
from util import vfs

import kaa
import kaa.rpc
from kaa import EventHandler
from kaa import AtTimer

appname = os.path.splitext(os.path.basename(sys.argv[0]))[0]
appconf = appname.upper()

# change uid
if __name__ == '__main__':
    config.DEBUG_STDOUT = 0
    uid = 'config.'+appconf+'_UID'
    gid = 'config.'+appconf+'_GID'
    try:
        if eval(uid) and os.getuid() == 0:
            os.setgid(eval(gid))
            os.setuid(eval(uid))
            os.environ['USER'] = pwd.getpwuid(os.getuid())[0]
            os.environ['HOME'] = pwd.getpwuid(os.getuid())[5]
    except Exception, why:
        print why
        sys.exit(1)

# The twisted modules will go away when all the recordserver clients
# have been updated to use kaa.rpc

from video.commdetectclient import initCommDetectJob
from video.commdetectclient import queueIt as queueCommdetectJob
from video.commdetectclient import listJobs as listCommdetectJobs
from video.commdetectclient import connectionTest as commdetectConnectionTest

from video.encodingclient import EncodingClientActions

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


DEBUG = hasattr(config, 'DEBUG_'+appconf) and eval('config.DEBUG_'+appconf) or config.DEBUG
LOGGING = hasattr(config, 'LOGGING_'+appconf) and eval('config.LOGGING_'+appconf) or config.LOGGING

logfile = '%s-%s.log' % (os.path.join(config.FREEVO_LOGDIR, appname), os.getuid())
sys.stdout = open(logfile, 'a')
sys.stderr = sys.stdout

logging.getLogger('').setLevel(LOGGING)
logging.basicConfig(level=LOGGING, \
                    #datefmt='%a, %H:%M:%S', # datefmt does not support msecs :(
                    format='%(asctime)s %(levelname)-8s %(message)s', \
                    filename=logfile, filemode='a')

try:
    import freevo.version as version
    import freevo.revision as revision
except:
    import version
    import revision

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

plugin.init_special_plugin(config.plugin_record)

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


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


class RecordServer:

    def __init__(self, debug=False):
        """ Initialise the Record Server class """
        _debug_('RecordServer.__init__(debug=%r)' % (debug), 2)
        self.debug = debug
        self.lock = threading.RLock()
        self.schedule_lock = threading.Lock()
        self.fc = FreevoChannels()
        # XXX: In the future we should have one lock per VideoGroup.
        self.tv_lockfile = None
        self.vg = None
        self.cardrecording=[]
        for i in range(len(config.VIDEO_GROUPS)):
            self.cardrecording.append("")
        self.previouslyRecordedShows = None
        self.delayed_recording = None
        self.schedule = ScheduledRecordings()
        self.updateFavoritesSchedule()
        self.es = EncodingClientActions()


    @kaa.rpc.expose('ping')
    def pingtest(self):
        _debug_('pingtest()', 2)
        return True


    @kaa.rpc.expose('isRecording')
    def isRecording(self):
        _debug_('isRecording()', 2)
        recording = glob.glob(os.path.join(config.FREEVO_CACHEDIR + 'record.*'))
        return (len(recording) > 0, recording)


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


    def findOverlaps(self, schedule):
        _debug_('findOverlaps(schedule=%r)' % (schedule,), 2)
        progs = schedule.getProgramList()
        proglist = list(progs)
        proglist.sort(self.progsTimeCompare)
        for progitem in proglist:
            progs[progitem].overlap = 0
        #for i in range(0, len(proglist)-1):
        #    thisprog = progs[proglist[i]]
        #    nextprog = progs[proglist[i+1]]
        #    if thisprog.stop > nextprog.start:
        #        thisprog.overlap = 1
        #        nextprog.overlap = 1
        #        _debug_('Overlap:\n%s\n%s' % (thisprog, nextprog), DINFO)


    @kaa.rpc.expose('findNextProgram')
    def findNextProgram(self, isrecording=False):
        _debug_('findNextProgram(isrecording=%r)' % (isrecording), 2)

        next_program = None
        progs = self.getScheduledRecordings().getProgramList()
        proglist = list(progs)
        proglist.sort(self.progsTimeCompare)
        now = self.timenow()
        timenow = time.localtime(now)
        for progitem in proglist:
            prog = progs[progitem]
            _debug_('%s' % (prog), 2)

            if now >= prog.start-config.TV_RECORD_PADDING_PRE and now < prog.stop+config.TV_RECORD_PADDING_POST:
                recording = True
                if prog.isrecording:
                    _debug_('isrecording is %s' % (prog), 2)
                    return (True, prog)
            else:
                recording = False

            endtime = time.strftime(config.TV_TIME_FORMAT, time.localtime(prog.stop+config.TV_RECORD_PADDING_POST))
            _debug_('%s is recording %s stopping at %s' % (prog.title, recording and 'yes' or 'no', endtime), 2)

            if now > prog.stop + config.TV_RECORD_PADDING_POST:
                _debug_('%s: finished %s > %s' % (prog.title, time.strftime('%H:%M:%S', timenow), endtime), 2)
                continue

            if not recording:
                next_program = prog
                break

        if next_program is None:
            _debug_('No program scheduled to record', 2)
            return (False, next_program)

        _debug_('next is %s' % (next_program), 2)
        return (True, next_program)


    @kaa.rpc.expose('isPlayerRunning')
    def isPlayerRunning(self):
        """
        Test is the player is running

        TODO: real player running test, check /dev/videoX.  This could go into the upsoon client
        @returns: the state of a player, mplayer, xine, etc.
        """
        _debug_('isPlayerRunning()', 2)
        res = (os.path.exists(os.path.join(config.FREEVO_CACHEDIR, 'playing')))
        _debug_('isPlayerRunning=%r' % (res), 2)
        return res


    @kaa.rpc.expose('getScheduledRecordings')
    def getScheduledRecordings(self):
        _debug_('getScheduledRecordings()', 2)
        file_ver = None
        schedule = None

        if os.path.isfile(config.TV_RECORD_SCHEDULE):
            _debug_('reading cache (%r)' % config.TV_RECORD_SCHEDULE, 2)
            if hasattr(self, 'schedule_cache'):
                mod_time, schedule = self.schedule_cache
                try:
                    if os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME] == mod_time:
                        _debug_('using cached schedule', 2)
                        return schedule
                except OSError, why:
                    _debug_('Failed to stat %r: %s' % (config.TV_RECORD_SCHEDULE, why), DERROR)
                    pass

            schedule = self.schedule.loadRecordSchedule()

            try:
                file_ver = schedule.TYPES_VERSION
            except AttributeError:
                _debug_('The cache does not have a version and must be recreated', DINFO)

            if file_ver != TYPES_VERSION:
                _debug_('ScheduledRecordings version number %s is stale (new is %s), must be reloaded' % \
                        (file_ver, TYPES_VERSION), DINFO)
                schedule = None
            else:
                _debug_('Got ScheduledRecordings (version %s).' % file_ver, DINFO)

        if not schedule:
            _debug_('Created a new ScheduledRecordings', DINFO)
            schedule = ScheduledRecordings()
            self.saveScheduledRecordings(schedule)

        _debug_('ScheduledRecordings has %s items.' % len(schedule.program_list))

        try:
            mod_time = os.stat(config.TV_RECORD_SCHEDULE)[stat.ST_MTIME]
            self.schedule_cache = mod_time, schedule
        except OSError:
            pass
        return schedule


    #@kaa.rpc.expose('saveScheduledRecordings')
    def saveScheduledRecordings(self, schedule=None):
        """ Save the schedule to disk """
        _debug_('saveScheduledRecordings(schedule=%r)' % (schedule), 2)

        if not schedule:
            _debug_('making a new ScheduledRecordings', DINFO)
            schedule = ScheduledRecordings()

        self.findOverlaps(schedule)

        schedule.saveRecordSchedule()

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

        return (True, _('scheduled recordings saved'))


    def loadPreviouslyRecordedShows(self):
        """ Load the saved set of recorded shows """
        _debug_('loadPreviouslyRecordedShows()', 2)
        if self.previouslyRecordedShows:
            return

        cacheFile = os.path.join(config.FREEVO_CACHEDIR, 'previouslyRecorded.pickle')
        try:
            self.previouslyRecordedShows = pickle.load(open(cacheFile, 'r'))
        except IOError:
            self.previouslyRecordedShows = {}
            pass


    def savePreviouslyRecordedShows(self):
        """ Save the set of recorded shows """
        _debug_('savePreviouslyRecordedShows()', 2)
        if not self.previouslyRecordedShows:
            return

        cacheFile = os.path.join(config.FREEVO_CACHEDIR, 'previouslyRecorded.pickle')
        pickle.dump(self.previouslyRecordedShows, open(cacheFile, 'w'))


    def newEpisode(self, prog=None):
        """ Return true if this is a new episode of 'prog' """
        _debug_('newEpisode(prog=%r)' % (prog,), 2)
        todayStr = datetime.date.today().strftime('%Y%m%d')
        progStr = str(prog.date)
        _debug_('Program Date: "%s"' % progStr, DINFO)
        _debug_('Todays Date : "%s"' % todayStr, DINFO)
        if (len(progStr) == 8):
            _debug_('Good date format', DINFO)
            #Year
            todaysYear = (todayStr[0:4])
            progYear = (progStr[0:4])
            #Month
            todaysMonth = (todayStr[4:-2])
            progMonth = (progStr[4:-2])
            #Day
            todaysDay = (todayStr[6:])
            progDay = (progStr[6:])
            if todaysYear > progYear:
                #program from a previous year
                return False
            elif progYear > todaysYear:
                #program in the future
                return True
            else:
                _debug_('Same year', DINFO)
                #program in the same year
                if todaysMonth > progMonth:
                    #program in a previous month
                    return False
                elif progMonth > todaysMonth:
                    #program in the future
                    return True
                else:
                    _debug_('Same month', DINFO)
                    #program in the same month
                    if todaysDay > progDay:
                        #program was previous aired this month
                        return False
                    else:
                        _debug_('Same day or in the upcoming month', DINFO)
                        #program is today or in the upcoming days
                        return True
        else:
            _debug_('No good date format, assuming new Episode to be on the safe side', DINFO)
            return True


    def shrink(self, text):
        """ Shrink a string by removing all spaces and making it
        lower case and then returning the MD5 digest of it. """
        _debug_('shrink(text=%r)' % (text,), 2)
        if text:
            text = hashlib.md5(text.lower().replace(' ', '')).hexdigest()
        else:
            text = ''
        return text


    def getPreviousRecordingKey(self, prog):
        """Return the key to be used for a given prog in the
        previouslyRecordedShows hashtable."""
        _debug_('getPreviousRecordingKey(prog=%r)' % (prog,), 2)
        shrunkTitle = self.shrink(prog.title)
        shrunkSub   = self.shrink(prog.sub_title)
        shrunkDesc  = self.shrink(prog.desc);
        return ('%s-%s-%s' % (shrunkTitle, shrunkSub, shrunkDesc), \
                '%s-%s-'   % (shrunkTitle, shrunkSub),             \
                '%s--%s'   % (shrunkTitle, shrunkDesc))


    def getPreviousRecording(self, prog):
        """Get a previous recording, or None if none."""
        _debug_('getPreviousRecording(prog=%r)' % (prog,), 2)
        try:
            return self.previouslyRecordedShows[self.getPreviousRecordingKey(prog)]
        except KeyError:
            return None


    def removeDuplicate(self, prog=None):
        """Remove a duplicate recording"""
        _debug_('removeDuplicate(prog=%r)' % (prog,), 2)
        self.loadPreviouslyRecordedShows()
        previous = self.getPreviousRecording(prog)
        if previous:
            _debug_('Found duplicate, removing', DINFO)
            del self.previouslyRecordedShows[self.getPreviousRecordingKey(previous)]
            self.savePreviouslyRecordedShows()


    def addDuplicate(self, prog=None):
        """Add program to duplicates hash"""
        _debug_('addDuplicate(prog=%r)' % (prog,), 2)
        self.loadPreviouslyRecordedShows()
        self.previouslyRecordedShows[self.getPreviousRecordingKey(prog)] = prog
        for key in self.getPreviousRecordingKey(prog):
            self.previouslyRecordedShows[key] = prog.start
        self.savePreviouslyRecordedShows()


    def duplicate(self, prog=None):
        """Identify if the given programme is a duplicate. If not,
        record it as previously recorded."""
        _debug_('duplicate(prog=%r)' % (prog,), 2)
        self.loadPreviouslyRecordedShows()
        previous = self.getPreviousRecording(prog)
        if previous:
            _debug_('Found duplicate for "%s", "%s", "%s", not adding' % \
                    (prog.title, prog.sub_title, prog.desc), 2)
            return True
        return False


    def addRecordingToSchedule(self, prog=None, inputSchedule=None):
        _debug_('addRecordingToSchedule(%r, inputSchedule=%r)' % (prog, inputSchedule), 2)
        if inputSchedule:
            schedule = inputSchedule
        else:
            schedule = self.getScheduledRecordings()
        schedule.addProgram(prog, tv_util.getKey(prog))
        if not inputSchedule:
            if config.TV_RECORD_DUPLICATE_DETECTION:
                self.addDuplicate(prog)
            self.saveScheduledRecordings(schedule)


    def removeRecordingFromSchedule(self, prog=None, inputSchedule=None):
        _debug_('removeRecordingFromSchedule(%r, inputSchedule=%r)' % (prog, inputSchedule), 2)
        if inputSchedule:
            schedule = inputSchedule
        else:
            schedule = self.getScheduledRecordings()
        schedule.removeProgram(prog, tv_util.getKey(prog))
        if not inputSchedule:
            if config.TV_RECORD_DUPLICATE_DETECTION:
                self.removeDuplicate(prog)
            self.saveScheduledRecordings(schedule)


    def conflictResolution(self, prog):
        _debug_('conflictResolution(prog=%r)' % (prog,), 2)
        def exactMatch(self, prog):
            _debug_('exactMatch(prog=%r)' % (prog,), 2)
            if prog.desc:
                descResult = False
                descMatches = None
                (descResult, descMatches) = self.findMatches(prog.desc)
                if descResult:
                    _debug_('Exact Matches %s' % (len(descMatches)), DINFO)
                    return descMatches

            if prog.sub_title:
                sub_titleResult = False
                sub_titleMatches = None
                (sub_titleResult, sub_titleMatches) = self.findMatches(prog.sub_title)
                if sub_titleResult:
                    _debug_('Exact Matches %s' % (len(sub_titleMatches)), DINFO)
                    return sub_titleMatches
            return None


        def getConflicts(self, prog, myScheduledRecordings):
            _debug_('getConflicts(prog=%r, myScheduledRecordings=%r)' % (prog, myScheduledRecordings), 2)
            self.addRecordingToSchedule(prog, myScheduledRecordings)
            progs = myScheduledRecordings.getProgramList()
            proglist = list(progs)
            proglist.sort(self.progsTimeCompare)
            conflictRating = 0
            conflicts = []
            for i in range(0, len(proglist)-1):
                thisprog = progs[proglist[i]]
                nextprog = progs[proglist[i+1]]
                if thisprog.stop > nextprog.start:
                    conflictRating = conflictRating+1
                    if thisprog == prog:
                        conflicts.append(nextprog)
                    elif nextprog == prog:
                        conflicts.append(thisprog)
            self.removeRecordingFromSchedule(prog, myScheduledRecordings)
            return (conflictRating, conflicts)


        def getRatedConflicts(self, prog, myScheduledRecordings):
            _debug_('getRatedConflicts(prog=%r, myScheduledRecordings=%r)' % (prog, myScheduledRecordings), 2)
            ratedConflicts = []
            occurances = exactMatch(self, prog)
            if not occurances:
                #program no longer exists
                return (False, None, [])
            #Search through all occurances of looking for a non-conflicted occurance
            for oneOccurance in occurances:
                (rating, conflictedProgs) = getConflicts(self, oneOccurance, myScheduledRecordings)
                if rating == 0:
                    _debug_('No Conflict', DINFO)
                    programsToChange = []
                    programsToChange.append(('add', oneOccurance))
                    return(True, ratedConflicts, programsToChange)
                _debug_('Conflict Found', DINFO)
                ratedConflicts.append((rating, conflictedProgs, oneOccurance))
            return (False, ratedConflicts, [])

        if config.TV_RECORD_CONFLICT_RESOLUTION:
            ratedConflicts = []
            myScheduledRecordings = copy.deepcopy(self.getScheduledRecordings())

            #Try to record it at its listed time
            (rating, conflictedProg) = getConflicts(self, prog, myScheduledRecordings)
            if rating == 0:
                #No need to do anything fancy; this will work at its default time
                progsToChange = []
                progsToChange.append(('add', prog))
                return (True, 'No conflicts, using default time', progsToChange)

            #Default time didn't work, let's try all times known
            (result, ratedConflicts, progsToChange) = getRatedConflicts(self, prog, myScheduledRecordings)
            if result:
                #No conflicts
                return (True, 'No conflicts if new program is added', progsToChange)
            if not ratedConflicts:
                #Program no longer exists, should never hit this unless schedule changes
                return (False, 'Cannot schedule, new prog no longer exists', None)

            _debug_('Going into conflict resolution via scheduled program re-scheduling', DINFO)
            # No viable time to schedule the program without a conflict
            # Try and reschedule the already scheduled program
            atleastOneSingleConflict = False
            for (scheduledConflictRating, scheduledConflictPrograms, conflictProgram) in ratedConflicts:
                #Only handle one conflict at the moment
                if scheduledConflictRating == 1:
                    atleastOneSingleConflict = True
                    scheduledConflictProgram = scheduledConflictPrograms[0]
                    #remove already scheduled program and try to reschedule it with the new program
                    self.removeRecordingFromSchedule(scheduledConflictProgram, myScheduledRecordings)
                    self.addRecordingToSchedule(conflictProgram, myScheduledRecordings)
                    (result, ratedConflicts, progsToChange) = getRatedConflicts(self, \
                                                                                scheduledConflictProgram, myScheduledRecordings)
                    if result:
                        #No conflicts
                        progsToChange.append(('del', scheduledConflictProgram))
                        progsToChange.append(('add', conflictProgram))
                        return (True, 'No conflicts if scheduled program is rescheduled', progsToChange)
                    if not ratedConflicts:
                        #Program no longer exists, should never hit this unless schedule changes
                        progsToChange.append(('del', scheduledConflictProgram))
                        progsToChange.append(('add', conflictProgram))
                        return (True, 'Cannot find conflicted program, adding new', progsToChange)
                    #Return this to original state
                    self.addRecordingToSchedule(scheduledConflictProgram, myScheduledRecordings)
                    self.removeRecordingFromSchedule(conflictProgram, myScheduledRecordings)
            if not atleastOneSingleConflict:
                #Dirty way to (not) handle multiple conflicts
                return (False, 'Cannot handle multiple conflicts: %s not scheduled' % (prog.title), None)

            _debug_('Going into conflict resolution via priority', DINFO)
            # No viable option to reschedule the original program
            # Time to resolve the conflict via priority
            tempRating = 1000
            tempConflicted = None
            tempProg = None
            #Find least conflicted
            for (conflictedRating, conflictedPrograms, tempProgram) in ratedConflicts:
                #Cannot handle multiple conflicts
                if conflictedPrograms:
                    conflictedProgram = conflictedPrograms[0]
                    if conflictedRating < tempRating:
                        tempRating = conflictedRating
                        tempConflicted = conflictedProgram
                        tempProg = tempProgram
            conflictedProgram = tempConflicted
            prog = tempProgram

            #Here is where it gets ugly
            (isProgFav, progFav) = self.getFavoriteObject(prog)
            (isConfFav, confFav) = self.getFavoriteObject(conflictedProgram)
            if not isProgFav and isConfFav:
                #Regular recording has higher priority then favorite
                progsToChange = []
                progsToChange.append(('del', conflictedProgram))
                progsToChange.append(('add', prog))
                reason = 'New program is a regular recording(added), scheduled is a Favorite(removed)'
                return (True, reason, progsToChange)
            elif isProgFav and not isConfFav:
                #Regular recording has higher priority then favorite
                progsToChange = []
                progsToChange.append(('del', prog))
                progsToChange.append(('add', conflictedProgram))
                reason = 'Scheduled program is a regular recording(added), new is a Favorite(removed)'
                return (True, reason, progsToChange)
            elif not isProgFav and not isConfFav:
                return (False, 'Both are regular programs, not adding new recording', None)
            elif isProgFav and isConfFav:
                #Both are favorites, go by priority (lower is better)
                if progFav.priority < confFav.priority:
                    progsToChange = []
                    progsToChange.append(('del', conflictedProgram))
                    progsToChange.append(('add', prog))
                    reason = 'New program is higher rated(added), Scheduled is lower(removed)'
                    return (True, reason, progsToChange)
                elif confFav.priority < progFav.priority:
                    progsToChange = []
                    progsToChange.append(('del', prog))
                    progsToChange.append(('add', conflictedProgram))
                    reason = 'Scheduled program is higher rated(added), New is lower(removed)'
                    return (True, reason, progsToChange)
                else:
                    #Equal priority, not adding new program
                    return (False, 'Both are regular programs, not adding new recording', None)
            else:
                return (False, 'No viable way to schedule', None)
        else:
            progsToChange = []
            progsToChange.append(('add', prog))
            return (True, 'Conflict resolution disabled', progsToChange)


    def checkOnlyNewDetection(self, prog=None):
        _debug_('checkOnlyNewDetection(prog=%r)' % (prog,), 2)
        if config.TV_RECORD_ONLY_NEW_DETECTION:
            if not self.doesFavoriteRecordOnlyNewEpisodes(prog):
                return (True, 'Favorite records all episodes, record')
            if self.newEpisode(prog):
                return (True, 'New episode, record')
            else:
                return (False, 'Old episode, do not record')
        else:
            return (True, 'Only new episode detection disabled, record')


    def checkDuplicateDetection(self, prog=None):
        _debug_('checkDuplicateDetection(prog=%r)' % (prog,), 2)
        if config.TV_RECORD_DUPLICATE_DETECTION:
            if self.doesFavoriteAllowDuplicates(prog):
                return (True, 'Favorite allows duplicates, record')
            if not self.duplicate(prog):
                return (True, 'Not a duplicate, record')
            else:
                return (False, 'Duplicate recording, do not record')
        else:
            return (True, 'Duplicate detection is disabled, record')


    def setTunerid(self, prog):
        _debug_('setTunerid(prog=%r)' % (prog,), 2)
        for chan in guide.chan_list:
            if prog.channel_id == chan.id:
                prog.tunerid = chan.tunerid
                _debug_('%s tuner: %s' % (prog, prog.tunerid), 2)
        return prog


    @kaa.rpc.expose('scheduleRecording')
    def scheduleRecording(self, prog=None):
        """
        """
        _debug_('scheduleRecording(%r)' % (prog,), 2)
        global guide

        if prog is None:
            return (False, _('program is not set'))

        now = self.timenow()
        if now > prog.stop:
            return (False, _('program cannot record as it is over'))

        self.updateGuide()

        (isFav, favorite) = self.isProgAFavorite(prog)
        if isFav:
            (onlyNewBool, onlyNewReason) = self.checkOnlyNewDetection(prog)
            _debug_('Only new episode detection: %s reason %s' % (onlyNewBool, onlyNewReason), 2)
            if not onlyNewBool:
                #failed only new episode check (old episode, etc)
                return (False, onlyNewReason)

            (duplicateBool, duplicateReason) = self.checkDuplicateDetection(prog)
            _debug_('Duplicate detection: %s reason %s' % (duplicateBool, duplicateReason), 2)
            if not duplicateBool:
                #failed duplicate check (duplicate, etc)
                return (False, duplicateReason)

        (ableToResolveBool, resolutionReason, progsToChange) = self.conflictResolution(prog)
        _debug_('Conflict resolution: %s reason %s' % (ableToResolveBool, resolutionReason), 2)
        if not ableToResolveBool:
            #No viable solution was found
            return (False, resolutionReason)

        if progsToChange:
            for (cmd, prog) in progsToChange:
                prog = self.setTunerid(prog)
                if cmd == 'add':
                    _debug_('adding %s to schedule' % (prog.title), 2)
                    self.addRecordingToSchedule(prog)
                elif cmd == 'del':
                    _debug_('removed %s from schedule' % (prog.title), 2)
                    self.removeRecordingFromSchedule(prog)
        else:
            prog = self.setTunerid(prog)
            _debug_('added %s to schedule' % (prog.title), DINFO)
            self.addRecordingToSchedule(prog)

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

        return (True, _('program scheduled to record'))


    @kaa.rpc.expose('removeScheduledRecording')
    def removeScheduledRecording(self, prog=None):
        _debug_('removeScheduledRecording(prog=%r)' % (prog,), 2)
        if prog is None:
            return (False, _('program is not set'))

        schedule = self.getScheduledRecordings()
        progs = schedule.getProgramList()

        for saved_prog in progs.values():
            if String(saved_prog) == String(prog):
                prog = saved_prog
                break

        recording = hasattr(prog, 'isRecording') and prog.isRecording

        self.removeRecordingFromSchedule(prog)

        now = self.timenow()

        # if now >= prog.start and now <= prog.stop and recording:
        if recording:
            _debug_('stopping current recording %s' % (prog), DINFO)
            rec_plugin = plugin.getbyname('RECORD')
            if rec_plugin:
                try:
                    rec_plugin.Stop( prog.cardID )
                except AttributeError:
                    _debug_('NOT CALLED RECORD STOP ', DINFO)

        return (True, _('program removed from record schedule'))


    @kaa.rpc.expose('isProgScheduled')
    def isProgScheduled(self, prog, schedule=None):
        _debug_('isProgScheduled(prog=%r, schedule=%r)' % (prog, schedule), 2)

        if schedule is None:
            schedule = self.getScheduledRecordings()
            if schedule is None:
                return (False, _('scheduled programs is empty'))

        if schedule.getProgramList() == {}:
            return (False, _('program list is empty'))

        for me in schedule.getProgramList().values():
            if me.start == prog.start and me.channel_id == prog.channel_id:
                return (True, _('program is scheduled'))
        return (False, _('program is not scheduled'))


    @kaa.rpc.expose('findProg')
    def findProg(self, chan=None, start=None):
        _debug_('findProg(chan=%r, start=%r' % (chan, start), 2)
        global guide

        if chan is None or start is None:
            return (False, None)

        self.updateGuide()

        for ch in guide.chan_list:
            if chan == ch.id:
                _debug_('CHANNEL MATCH: %s' % ch.id, DINFO)
                for prog in ch.programs:
                    if start == '%s' % prog.start:
                        _debug_('PROGRAM MATCH 1: %s' % prog, DINFO)
                        return (True, prog.utf2str())

        return (False, None)


    @kaa.rpc.expose('findMatches')
    def findMatches(self, find=None, movies_only=False):
        _debug_('findMatches(find=%r, movies_only=%r)' % (find, movies_only), 2)
        global guide

        matches = []
        max_results = 500

        if find:
            find = Unicode(find).replace('(','\(').replace(')','\)').replace('.','\.')
        elif not movies_only:
            _debug_('nothing to find', DINFO)
            return (False, _('nothing to find'))

        self.updateGuide()

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

        for ch in guide.chan_list:
            for prog in ch.programs:
                if now >= prog.stop:
                    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.utf2str().getattr('ratings').keys():
                            matches.append(prog.utf2str())
                            _debug_('PROGRAM MATCH 2: %s' % prog, DINFO)
                    else:
                        matches.append(prog.utf2str())
                        _debug_('PROGRAM MATCH 3: %s' % prog, DINFO)
                if len(matches) >= max_results:
                    break

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

        if len(matches) == 0:
            return (False, _('no programs match'))
        return (True, matches)

    def findCard(self,prog):
        for i in range(len(self.cardrecording)):
            if prog.title+"%s %s"%(prog.channel_id, prog.start)==self.cardrecording[i]:
                return i

    def updateGuide(self):
        _debug_('updateGuide()', 2)
        global guide
        guide = tv.epg_xmltv.get_guide()


    def addFavoritesToSchedule(self):
        _debug_('addFavoritesToSchedule()', 2)
        pass


    def timenow(self):
        """
        Round up the timer to the nearest minute; the loop runs once a minute.
        The RECORDSERVER_ATTIMER is a value to allow the recording to start at
        the number of seconds of the minute. The AtTimer is not 100% accurate
        need to allow a few seconds for it.

        @return the time in seconds
        """
        now = list(time.localtime(time.time() + 60 - 3))
        # round down to the nearest minute
        now[5] = 0
        return time.mktime(now)


    def check_to_record(self):
        """
        This is the real main loop of the record server
        """
        now = self.timenow()
        _debug_('check_to_record %s' % (time.strftime('%H:%M:%S')), 2)
        rec_cmd = None
        rec_prog = None
        cleaned = None
        rec_progs = []

        schedule = self.getScheduledRecordings()
        if schedule is None:
            _debug_('no scheduled recordings')
            return

        progs = schedule.getProgramList()

        currently_recording = None
        currently_recordings = []
        for prog in progs.values():
            recording = hasattr(prog, 'isRecording') and prog.isRecording
            if recording:
                currently_recordings.append(prog)
                currently_recording = prog
        print "currently_recordings=",currently_recordings
        if len(currently_recordings)<len(config.VIDEO_GROUPS):
            #THIS is 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"
        #if currently_recording is not None:
        #    _debug_('currently_recording=%s' % (currently_recording), DINFO)
        if self.delayed_recording is not None:
            _debug_('delayed_recording=%s' % (self.delayed_recording), DINFO)

        for prog in progs.values():
            recording = hasattr(prog, 'isRecording') and prog.isRecording

            _debug_('prog=%s recording=%s' % (prog, recording), 2)

            if not recording and \
               now >= (prog.start - config.TV_RECORD_PADDING_PRE) and \
               now < (prog.stop + config.TV_RECORD_PADDING_POST):
                # 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) - int(now)
                if duration < 10:
                    _debug_('duration %s too small' % duration, DINFO)
                    return

                if currently_recording:
                    #hmm what now we have a conflict
                    # is a show on one of the channels ending? If so pick that on
                    print "trying to resolve conflict. Is there a show that should end at", prog.start
                    for currently_recording in  currently_recordings:
                        print "testing:",currently_recording.title,currently_recording.stop,currently_recording.start
                        if currently_recording.stop - 10 <= prog.start:
                            # The only reason we are still recording is because of
                            # the post-record padding.
                            #kill this one
                            print "found it"
                            break
                    #Ok at the end of this loop we simply take the last one examined
                    # Hey, something is already recording!
                    overlap_duration = currently_recording.stop - prog.start
                    _debug_('overlap_duration=%r' % overlap_duration)
                    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 \
                           now < (prog.stop + config.TV_RECORD_PADDING_POST):
                            _debug_('Ignoring %s' % prog, DINFO)
                            continue
                        schedule.removeProgram(currently_recording, tv_util.getKey(currently_recording))
                        try:
                            plugin.getbyname('RECORD').Stop(currently_recording.cardID)
                            _debug_('CALLED RECORD STOP 1: %s' % currently_recording, DINFO)
                        except AttributeError:
                             _debug_('NOT CALLED RECORD STOP 1: %s' % currently_recording, DINFO)

                    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):
                                schedule.removeProgram(currently_recording,
                                                       tv_util.getKey(currently_recording))
                                try:
                                    plugin.getbyname('RECORD').Stop(currently_recording.cardID)
                                    _debug_('CALLED RECORD STOP 2: %s' % currently_recording, DINFO)
                                except AttributeError:
                                    _debug_('NOT CALLED RECORD STOP 2: %s' % currently_recording, DINFO)

                    self.delayed_recording = prog
                else:
                    self.delayed_recording = None

                if self.delayed_recording:
                    _debug_('delaying: %s' % prog, DINFO)
                else:
                    _debug_('going to record: %s for %s  '% (prog,duration))
                    prog.isRecording = True
                    prog.rec_duration = duration + config.TV_RECORD_PADDING_POST
                    prog.filename = tv_util.getProgFilename(prog)
                    rec_progs.append(prog)

        for prog in progs.values():
            # If the program is over remove the entry.
            if now > (prog.stop + config.TV_RECORD_PADDING_POST +42 ):
                _debug_('found a program to clean: %s' % prog, DINFO)
                try:
                    plugin.getbyname('RECORD').Stop( prog.cardID)
                except AttributeError:
                    _debug_('NOT STOPPED: %s' % prog, DINFO)
                cleaned = True
                del progs[tv_util.getKey(prog)]

        if rec_progs or cleaned:
            schedule.setProgramList(progs)
            self.saveScheduledRecordings(schedule)

        if rec_progs:
            for rec_prog in rec_progs:
                _debug_('start recording: %s' % rec_prog)
                self.record_app = plugin.getbyname('RECORD')

                if not self.record_app:
                    print_plugin_warning()
                    _debug_('Recording %s failed.' % rec_prog.title, DERROR)
                    self.removeScheduledRecording(rec_prog)
                    return

                self.vg = self.fc.getVideoGroup(rec_prog.channel_id, False, CHANNEL_ID)
                res=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
                if res:
                    cardID,vdev = res
                    #print "VDEV=",vdev
                    suffix=vdev.split('/')[-1]
                    #print "SUFFIX=",suffix
                    rec_prog.tv_lockfile = config.FREEVO_CACHEDIR + '/record.'+suffix
                    rec_prog.cardID = cardID
                    self.cardrecording[cardID]=rec_prog.title+"%s %s"%(rec_prog.channel_id, rec_prog.start)

            # Cleanup old recordings (if enabled)
            if config.RECORDSERVER_CLEANUP_THRESHOLD > 0:
                space_threshold = config.RECORDSERVER_CLEANUP_THRESHOLD * 1024 * 1024 * 1024
                path = config.TV_RECORD_DIR
                freespace = util.freespace(path)
                if freespace < space_threshold:
                    files = os.listdir(path)
                    files = util.find_matches(files, config.VIDEO_SUFFIX)
                    files = [(f, os.stat(os.path.join(path, f)).st_mtime) for f in files]
                    files.sort(lambda x, y: cmp(x[1], y[1]))
                    i = 0
                    while freespace < space_threshold and i < len(files):
                        oldestrec = files[i][0]
                        oldestfxd = oldestrec[:oldestrec.rfind('.')] + '.fxd'
                        _debug_('Low on disk space - delete oldest recording: %s' % oldestrec, DINFO)
                        os.remove(os.path.join(path, oldestrec))
                        os.remove(os.path.join(path, oldestfxd))
                        freespace = util.freespace(path)
                        i = i + 1


    def handleEvents(self, event):
        if event:
            if event == RECORD_START:
                prog = event.arg
                _debug_('RECORD_START %s' % (prog), DINFO)
                open(prog.tv_lockfile, 'w').close()
                self.create_fxd(prog)
                if config.VCR_PRE_REC:
                    util.popen3.Popen3(config.VCR_PRE_REC)

            elif event == RECORD_STOP:
                prog = event.arg
                _debug_('RECORD_STOP %s' % (prog), DINFO)
                prog.isRecording = False

                # Create and run the post processing thread
                postprocess = RecordPostProcess(prog)
                postprocess.setDaemon(0) # wait for the thread to end
                postprocess.start()

                # This is a really nasty hack but if it fixes the problem then great
                if self.delayed_recording:
                    self.delayed_recording = None
                    self.check_to_record()
                #else:

                os.remove(prog.tv_lockfile)

            else:
                if hasattr(event, 'arg'):
                    _debug_('event=%s arg=%r not handled' % (event, event.arg), DWARNING)
                else:
                    _debug_('event=%s not handled' % (event), DWARNING)

        else:
            # Should never happen
            _debug_('event not defined', DERROR)


    @kaa.rpc.expose('addFavorite')
    def addFavorite(self, name, prog, exactchan=False, exactdow=False, exacttod=False):
        _debug_('addFavorite(name=%r, prog=%r, exactchan=%r, exactdow=%r, exacttod=%r)' % \
                (name, prog, exactchan, exactdow, exacttod), 2)
        if not name:
            return (False, _('no favorite name'))

        favs = self.getFavorites()
        priority = len(favs) + 1
        fav = tv.record_types.Favorite(name, prog, exactchan, exactdow, exacttod, priority, allowDuplicates, onlyNew)

        schedule = self.getScheduledRecordings()
        schedule.addFavorite(fav)
        self.saveScheduledRecordings(schedule)
        self.addFavoriteToSchedule(fav)

        return (True, _('favorite added'))


    @kaa.rpc.expose('addEditedFavorite')
    def addEditedFavorite(self, name, title, chan, dow, mod, priority, allowDuplicates, onlyNew):
        _debug_('addEditedFavorite(name=%r, title=%r, chan=%r, dow=%r, mod=%r, priority=%r, allowDuplicates=%r, onlyNew=%r)' % (name, title, chan, dow, mod, priority, allowDuplicates, onlyNew), 2)
        fav = tv.record_types.Favorite()

        fav.name = name
        fav.title = title
        fav.channel = chan
        fav.dow = dow
        fav.mod = mod
        fav.priority = priority
        fav.allowDuplicates = allowDuplicates
        fav.onlyNew = onlyNew

        schedule = self.getScheduledRecordings()
        schedule.addFavorite(fav)
        self.saveScheduledRecordings(schedule)
        self.addFavoriteToSchedule(fav)

        return (True, _('favorite added'))


    @kaa.rpc.expose('removeFavorite')
    def removeFavorite(self, name=None):
        _debug_('removeFavorite(name=%r)' % (name), 2)
        if name is None:
            return (False, _('name is not set'))

        (status, fav) = self.getFavorite(name)
        self.removeFavoriteFromSchedule(fav)
        schedule = self.getScheduledRecordings()
        schedule.removeFavorite(name)
        self.saveScheduledRecordings(schedule)

        return (True, _('favorite removed'))


    @kaa.rpc.expose('clearFavorites')
    def clearFavorites(self):
        _debug_('clearFavorites()', 2)
        schedule = self.getScheduledRecordings()
        schedule.clearFavorites()
        self.saveScheduledRecordings(schedule)

        return (True, _('favorites cleared'))


    @kaa.rpc.expose('getFavorites')
    def getFavorites(self):
        _debug_('getFavorites()', 2)
        return self.getScheduledRecordings().getFavorites()


    @kaa.rpc.expose('getFavorite')
    def getFavorite(self, name):
        _debug_('getFavorite(name=%r)' % (name), 2)
        favs = self.getFavorites()

        if favs.has_key(name):
            fav = favs[name]
            return (True, fav)
        else:
            return (False, _('not a favorite'))


    @kaa.rpc.expose('adjustPriority')
    def adjustPriority(self, favname, mod=0):
        _debug_('adjustPriority(favname=%r, mod=%r)' % (favname, mod), 2)
        save = []
        mod = int(mod)
        (status, me) = self.getFavorite(favname)
        oldprio = int(me.priority)
        newprio = oldprio + mod

        _debug_('ap: mod=%s' % mod, DINFO)

        schedule = self.getScheduledRecordings()
        favs = schedule.getFavorites().values()

        _debug_('adjusting prio of %r' % (favname,), DINFO)
        for fav in favs:
            fav.priority = int(fav.priority)

            if fav.name == me.name:
                _debug_('MATCH', DINFO)
                fav.priority = newprio
                _debug_('moved prio of %s: %s => %s' % (fav.name, oldprio, newprio), DINFO)
                continue
            if mod < 0:
                if fav.priority < newprio or fav.priority > oldprio:
                    _debug_('fp: %s, old: %s, new: %s' % (fav.priority, oldprio, newprio), DINFO)
                    _debug_('skipping: %s' % fav.name, DINFO)
                    continue
                fav.priority = fav.priority + 1
                _debug_('moved prio of %s: %s => %s' % (fav.name, fav.priority-1, fav.priority), DINFO)

            if mod > 0:
                if fav.priority > newprio or fav.priority < oldprio:
                    _debug_('skipping: %s' % fav.name, DINFO)
                    continue
                fav.priority = fav.priority - 1
                _debug_('moved prio of %s: %s => %s' % (fav.name, fav.priority+1, fav.priority), DINFO)

        schedule.setFavoritesList(favs)
        self.saveScheduledRecordings(schedule)

        return (True, _('priority adjusted'))


    @kaa.rpc.expose('getFavoriteObject')
    def getFavoriteObject(self, prog, favs=None):
        """ more liberal favorite check that returns an object """
        _debug_('getFavoriteObject(prog=%r, favs=%r)' % (prog, favs), 2)
        if not favs:
            favs = self.getFavorites()
        # first try the strict test
        name = tv_util.progname2favname(prog.title)
        if favs.has_key(name):
            fav = favs[name]
            return (True, fav)
        # try harder to find this favorite in a more liberal search
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
                return (True, fav)
        # if we get this far prog is not a favorite
        return (False, _('not a favorite'))


    @kaa.rpc.expose('isProgAFavorite')
    def isProgAFavorite(self, prog, favs=None):
        #_debug_('isProgAFavorite(prog=%s, favs=%r)' % (prog, favs), 2)
        _debug_('isProgAFavorite(%s)' % (prog,), 2)
        if not favs:
            favs = self.getFavorites()

        if favs is None:
            return (False, _('no favorites'))

        lt = time.localtime(prog.start)
        dow = '%s' % lt[6]
        mod = '%s' % ((lt[3]*60)+lt[4])

        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).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) == u'ANY' \
                           or abs(int(fav.mod) - int(mod)) <= config.TV_RECORD_FAVORITE_MARGIN:
                            return (True, fav.name)

        # if we get this far prog is not a favorite
        return (False, _('not a favorite'))


    def doesFavoriteRecordOnlyNewEpisodes(self, prog, favs=None):
        _debug_('doesFavoriteRecordOnlyNewEpisodes(prog=%r, favs=%r)' % (prog, favs), 2)
        if not favs:
            favs = self.getFavorites()
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
                if not hasattr(fav, 'onlyNew'):
                    return True
                _debug_('NEW: %s' % fav.onlyNew, DINFO)
                if fav.onlyNew == '1':
                    return True


    def doesFavoriteAllowDuplicates(self, prog, favs=None):
        _debug_('doesFavoriteAllowDuplicates(prog=%r, favs=%r)' % (prog, favs), 2)
        if not favs:
            favs = self.getFavorites()
        for fav in favs.values():
            if Unicode(prog.title).lower().find(Unicode(fav.title).lower()) >= 0:
                if not hasattr(fav, 'allowDuplicates'):
                    return True
                _debug_('DUP: %s' % fav.allowDuplicates, DINFO)
                if fav.allowDuplicates == '1':
                    return True


    @kaa.rpc.expose('removeFavoriteFromSchedule')
    def removeFavoriteFromSchedule(self, fav):
        _debug_('removeFavoriteFromSchedule(fav=%r)' % (fav), 2)
        # TODO: make sure the program we remove is not
        #       covered by another favorite.

        tmp = {}
        tmp[fav.name] = fav

        schedule = self.getScheduledRecordings()
        schedule.lock()
        progs = schedule.getProgramList()
        for prog in progs.values():
            (isFav, favorite) = self.isProgAFavorite(prog, tmp)
            if isFav:
                self.removeScheduledRecording(prog)

        schedule.unlock()
        return (True, _('favorite removed from schedule'))


    @kaa.rpc.expose('addFavoriteToSchedule')
    def addFavoriteToSchedule(self, fav):
        _debug_('addFavoriteToSchedule(fav=%r)' % (fav), 2)
        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 added to schedule'))


    @kaa.rpc.expose('updateFavoritesSchedule')
    def updateFavoritesSchedule(self):
        #  TODO: do not re-add a prog to record if we have
        #        previously decided not to record it.
        _debug_('updateFavoritesSchedule()', 2)

        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

        schedule = self.getScheduledRecordings()

        favs = self.getFavorites()
        if not len(favs):
            return False

        schedule.lock()

        # Then remove all scheduled favorites in that time-frame to
        # make up for schedule changes.
        progs = schedule.getProgramList()
        for prog in progs.values():
            # 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)

        schedule.unlock()
        return True


    def create_fxd(self, rec_prog):
        _debug_('create_fxd(rec_prog=%r)' % (rec_prog,), 2)
        from util.fxdimdb import FxdImdb, makeVideo
        fxd = FxdImdb()

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

        desc = rec_prog.desc.replace('\n\n','\n').replace('\n','&#10;')
        video = makeVideo('file', 'f1', os.path.basename(rec_prog.filename))
        fxd.setVideo(video)
        fxd.info['channel'] = fxd.str2XML(rec_prog.channel_id)
        fxd.info['tunerid'] = fxd.str2XML(rec_prog.tunerid)
        fxd.info['tagline'] = fxd.str2XML(rec_prog.sub_title)
        fxd.info['plot'] = fxd.str2XML(desc)
        fxd.info['runtime'] = None
        fxd.info['recording_timestamp'] = str(rec_prog.start)
        try:
            fxd.info['userdate'] = time.strftime(config.TV_RECORD_YEAR_FORMAT, time.localtime(rec_prog.start))
        except:
            fxd.info['userdate'] = time.strftime(config.TV_RECORD_YEAR_FORMAT)
        fxd.info['year'] = time.strftime('%m-%d ' + config.TV_TIME_FORMAT, time.localtime(rec_prog.start))
        fxd.title = rec_prog.title
        if plugin.is_active('tv.recordings_manager'):
            fxd.info['watched'] = 'False'
            fxd.info['keep'] = 'False'
        fxd.writeFxd()


    def handleAtTimer(self):
        _debug_('handleAtTimer()', 2)
        self.check_to_record()



class RecordPostProcess(Thread):
    def __init__(self, prog):
        Thread.__init__(self)
        self.prog = prog


    def run(self):
        _debug_('post-processing started for %s' % (self.prog), DINFO)

        try:
            snapshot(self.prog.filename)
        except:
            # If automatic pickling fails, use on-demand caching when
            # the file is accessed instead.
            os.rename(vfs.getoverlay(self.prog.filename + '.raw.tmp'),
                      vfs.getoverlay(os.path.splitext(self.prog.filename)[0] + '.png'))

        if config.VCR_POST_REC:
            util.popen3.Popen3(config.VCR_POST_REC)

        if config.TV_RECORD_REMOVE_COMMERCIALS:
            (result, response) = commdetectConnectionTest('connection test')
            if result:
                (status, idnr) = initCommDetectJob(self.prog.filename)
                (status, output) = listCommdetectJobs()
                _debug_(output, DINFO)
                (status, output) = queueCommdetectJob(idnr, True)
                _debug_(output, DINFO)
            else:
                _debug_('commdetect server not running', DINFO)

        if config.TV_REENCODE:
            result = self.es.ping()
            if result:
                source = self.prog.filename
                output = self.prog.filename
                multipass = config.REENCODE_NUMPASSES > 1

                (status, resp) = self.es.initEncodingJob(source, output, self.prog.title, None,
                                                         config.TV_REENCODE_REMOVE_SOURCE)
                _debug_('initEncodingJob:status:%s resp:%s' % (status, resp))

                idnr = resp

                (status, resp) = self.es.setContainer(idnr, config.REENCODE_CONTAINER)
                _debug_('setContainer:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.setVideoCodec(idnr, config.REENCODE_VIDEOCODEC, 0, multipass,
                                                       config.REENCODE_VIDEOBITRATE, config.REENCODE_ALTPROFILE)
                _debug_('setVideoCodec:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.setAudioCodec(idnr, config.REENCODE_AUDIOCODEC,
                                                       config.REENCODE_AUDIOBITRATE)
                _debug_('setAudioCodec:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.setNumThreads(idnr, config.REENCODE_NUMTHREADS)
                _debug_('setNumThreads:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.setVideoRes(idnr, config.REENCODE_RESOLUTION)
                _debug_('setVideoRes:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.listJobs()
                _debug_('listJobs:status:%s resp:%s' % (status, resp))

                (status, resp) = self.es.queueIt(idnr, True)
                _debug_('queueIt:status:%s resp:%s' % (status, resp))
            else:
                _debug_('encoding server not running', DINFO)

        _debug_('post-processing finished for %s' % (self.prog), DINFO)



def main():
    if config.TV_RECORD_CONFLICT_RESOLUTION:
        _debug_('Conflict resolution enabled', DINFO)

    socket = ('', config.RECORDSERVER_PORT)
    secret = config.RECORDSERVER_SECRET
    _debug_('socket=%r, secret=%r' % (socket, secret))

    recordserver = RecordServer()

    try:
        rpc = kaa.rpc.Server(socket, secret)
    except Exception:
        raise

    rpc.register(recordserver)

    eh = EventHandler(recordserver.handleEvents)
    eh.register()

    _debug_('kaa.AtTimer starting')
    kaa.AtTimer(recordserver.handleAtTimer).start(sec=config.RECORDSERVER_ATTIMER)
    _debug_('kaa.main starting')
    kaa.main.run()
    _debug_('kaa.main finished')



if __name__ == '__main__':
    import socket
    import glob

    sys.stdout = config.Logger(sys.argv[0] + ':stdout')
    sys.stderr = config.Logger(sys.argv[0] + ':stderr')

    locks = glob.glob(os.path.join(config.FREEVO_CACHEDIR, 'record.*'))
    for f in locks:
        _debug_('removed old record lock %r' % f, DINFO)
        os.remove(f)

    try:
        _debug_('main() starting', DINFO)
        main()
        _debug_('main() finished', DINFO)
    except SystemExit:
        _debug_('main() stopped', DINFO)
        pass
    except Exception, why:
        import traceback
        traceback.print_exc()
        _debug_(why, DWARNING)
videothumb.py (text/plain, 5.4 KB)
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# videothumb - create a thumbnail for video files
# -----------------------------------------------------------------------
# $Id: videothumb.py 11354 2009-03-15 17:13:03Z duncan $
#
# Notes: This is a bad hack. It creates a new process to make the
#        images with mplayer and than copy it to the location
#
#        Based on videothumb.py commited to the freevo wiki
#
# Todo:
#
# -----------------------------------------------------------------------
# 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, os, glob, shutil
from stat import *
import kaa
import config
import util
import threading

SNAPLOCK = threading.Lock()

def snapshot(videofile, imagefile=None, pos=None, update=True, popup=None):
    """
    make a snapshot of the videofile at position pos to imagefile
    """
    from subprocess import Popen, PIPE
    import kaa.imlib2 as Image
    import vfs
    import gui.PopupBox
    import osd

    # skip broken symlinks
    if os.path.islink(videofile) and not os.path.exists(videofile):
        return

    if not imagefile:
        imagefile = vfs.getoverlay(videofile + '.raw')

    if not update and os.path.isfile(imagefile) and \
           os.stat(videofile)[ST_MTIME] <= os.stat(imagefile)[ST_MTIME]:
        return

    if imagefile.endswith('.raw'):
        imagefile += '.tmp'

    if popup:
        pop = gui.PopupBox(text='Creating thumbnail for "%s"...' % Unicode(os.path.basename(videofile)),
            width=osd.get_singleton().width-(config.OSD_OVERSCAN_LEFT+config.OSD_OVERSCAN_RIGHT)-80)
        pop.show()

    args = [ config.MPLAYER_CMD, videofile, imagefile ]

    if pos != None:
        args.append(str(pos))

    with SNAPLOCK:
        command = [os.environ['FREEVO_SCRIPT'], '--execute=%s' % os.path.abspath(__file__) ] + args
        _debug_(' '.join(command))
        p = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
        (stdout, stderr) = p.communicate()
        if p.returncode:
            for line in stdout.split():
                _debug_(line)
            for line in stderr.split():
                _debug_(line, DINFO)

        if vfs.isfile(imagefile):
            try:
                image = Image.open(imagefile)
                if image.width > 255 or image.height > 255:
                    image.thumbnail((255,255))

                if image.mode == 'P':
                    image.mode = 'RGB'

                if image.width * 3 > image.height * 4:
                    # fix image with blank bars to be 4:3
                    nh = (image.width*3)/4
                    ni = Image.new((image.width, nh), from_format = image.mode)
                    ni.draw_rectangle((0,0), (image.width, nh), (0,0,0,255), True)
                    ni.blend(image, dst_pos=(0,(nh- image.height) / 2))
                    image = ni
                elif image.width * 3 < image.height * 4:
                    # strange aspect, let's guess it's 4:3
                    new_size = (image.width, (image.width*3)/4)
                    image = image.scale((new_size))

                # crop some pixels, looks better that way
                image = image.crop((4, 3), (image.width-8, image.height-6))
                # Pygame can't handle BGRA images
                if image.mode == 'BGRA':
                    image.mode = 'RGBA'
                if imagefile.endswith('.raw.tmp'):
                    f = vfs.open(imagefile[:-4], 'w')
                    data = (str(image.get_raw_data(format=image.mode)), image.size, image.mode)
                    f.write('FRI%s%s%5s' % (chr(image.width), chr(image.height), image.mode))
                    f.write(data[0])
                    f.close()
                    os.unlink(imagefile)
                else:
                    image.save(imagefile)
            except (OSError, IOError), why:
                _debug_('snapshot: %s' % why, DERROR)
        else:
            _debug_('no imagefile found for "%s"' % (Unicode(videofile)), DWARNING)
            _debug_('%r' % imagefile, 2)

    if popup:
        pop.destroy()

#
# main function, will be called when this file is executed, not imported
# args: mplayer, videofile, imagefile, [ pos ]
#

if __name__ == '__main__':
    from encodingcore import EncodingJob
    mplayer   = os.path.abspath(sys.argv[1])
    filename  = os.path.abspath(sys.argv[2])
    imagefile = os.path.abspath(sys.argv[3])

    encjob = EncodingJob(filename, imagefile, None, None)
    encjob._videothumb()
    encjob._wait()
    sys.exit(0)