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',' ')
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)