Update of /cvsroot/freevo/freevo/lib/pyepg
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv14953
Modified Files:
__init__.py config.py xmltv.py
Added Files:
channel.py guide.py program.py xmltv_parser.py
Removed Files:
epg_types.py epg_xmltv.py
Log Message:
o better index generation
o split into different files
o support for other parsers than xmltv
o internal caching
Index: config.py
===================================================================
RCS file: /cvsroot/freevo/freevo/lib/pyepg/config.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -d -r1.1 -r1.2
*** config.py 26 Jul 2004 12:45:56 -0000 1.1
--- config.py 10 Aug 2004 19:35:45 -0000 1.2
***************
*** 5,13 ****
- # The file format version number. It must be updated when incompatible
- # changes are made to the file format.
- EPG_VERSION = 5
-
-
TV_DATEFORMAT = '%e-%b' # Day-Month: 11-Jun
TV_TIMEFORMAT = '%H:%M' # Hour-Minute 14:05
--- 5,8 ----
--- NEW FILE: program.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# program.py - A program class for the epg
# -----------------------------------------------------------------------
# $Id: program.py,v 1.1 2004/08/10 19:35:45 dischi Exp $
#
# Notes:
# Todo:
#
# -----------------------------------------------------------------------
# $Log: program.py,v $
# Revision 1.1 2004/08/10 19:35:45 dischi
# o better index generation
# o split into different files
# o support for other parsers than xmltv
# o internal caching
#
#
# -----------------------------------------------------------------------
# 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 time
import config
NO_DATA = _('no data')
class TvProgram:
"""
All information about one TV program
"""
def __init__(self, title='', start=0, stop=0):
self.channel_id = ''
self.title = title
self.desc = ''
self.sub_title = ''
self.start = start
self.stop = stop
self.ratings = {}
self.advisories = []
self.categories = []
self.date = None
self.pos = None
if title == NO_DATA:
self.valid = False
else:
self.valid = True
# Due to problems with Twisted's marmalade this should not be changed
# to a boolean type.
self.scheduled = 0
def __unicode__(self):
"""
return as unicode for debug
"""
bt = time.localtime(self.start) # Beginning time tuple
et = time.localtime(self.stop) # End time tuple
begins = '%s-%02d-%02d %02d:%02d' % (bt[0], bt[1], bt[2], bt[3], bt[4])
ends = '%s-%02d-%02d %02d:%02d' % (et[0], et[1], et[2], et[3], et[4])
return u'%s to %s %3s ' % (begins, ends, self.channel_id) + \
self.title + u' (%s)' % self.pos
def __str__(self):
"""
return as string for debug
"""
return String(self.__unicode__())
def __cmp__(self, other):
"""
compare function, return 0 if the objects are identical, 1 otherwise
"""
if not other:
return 1
return self.title != other.title or \
self.start != other.start or \
self.stop != other.stop or \
self.channel_id != other.channel_id
def getattr(self, attr):
"""
return the specific attribute as string or an empty string
"""
if attr == 'start':
return Unicode(time.strftime(config.TV_TIMEFORMAT,
time.localtime(self.start)))
if attr == 'stop':
return Unicode(time.strftime(config.TV_TIMEFORMAT,
time.localtime(self.stop)))
if attr == 'date':
return Unicode(time.strftime(config.TV_DATEFORMAT,
time.localtime(self.start)))
if attr == 'time':
return self.getattr('start') + u' - ' + self.getattr('stop')
if hasattr(self, attr):
return getattr(self,attr)
return ''
--- NEW FILE: xmltv_parser.py ---
#
# xmltv.py - Python interface to XMLTV format, based on XMLTV.pm
#
# Copyright (C) 2001 James Oakley
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser 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
#
# Notes:
# - Uses qp_xml instead of DOM. It's way faster
# - Read and write functions use file objects instead of filenames
# - Unicode is removed on dictionary keys because the xmlrpclib marshaller
# chokes on it. It'll always be Latin-1 anyway... (famous last words)
#
# Yes, A lot of this is quite different than the Perl module, mainly to keep
# it Pythonic
#
# If you have any trouble: [EMAIL PROTECTED]
#
# Changes for Freevo:
# o change data_format to '%Y%m%d%H%M%S %Z'
# o delete all encode, return Unicode
# o add except AttributeError: for unhandled elements (line 250ff)
from xml.utils import qp_xml
from xml.sax import saxutils
import string, types, re
# The Python-XMLTV version
VERSION = "0.5.15"
# The date format used in XMLTV
date_format = '%Y%m%d%H%M%S %Z'
# Note: Upstream xmltv.py uses %z so remember to change that when syncing
date_format_notz = '%Y%m%d%H%M%S'
# These characters are illegal in XML
XML_BADCHARS = re.compile(u'[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]')
#
# Options. They may be overridden after this module is imported
#
# The extraction process could be simpler, building a tree recursively
# without caring about the element names, but it's done this way to allow
# special handling for certain elements. If 'desc' changed one day then
# ProgrammeHandler.desc() can be modified to reflect it
class _ProgrammeHandler:
"""
Handles XMLTV programme elements
"""
#
# <tv> sub-tags
#
def title(self, node):
return _readwithlang(node)
def sub_title(self, node):
return _readwithlang(node)
def desc(self, node):
return _readwithlang(node)
def credits(self, node):
return _extractNodes(node, self)
def date(self, node):
return node.textof()
def category(self, node):
return _readwithlang(node)
def language(self, node):
return _readwithlang(node)
def orig_language(self, node):
return _readwithlang(node)
def length(self, node):
data = {}
data['units'] = _getxmlattr(node, u'units')
try:
length = int(node.textof())
except ValueError:
pass
data['length'] = length
return data
def icon(self, node):
data = {}
for attr in (u'src', u'width', u'height'):
if node.attrs.has_key(('', attr)):
data[attr] = _getxmlattr(node, attr)
return data
def url(self, node):
return node.textof()
def country(self, node):
return _readwithlang(node)
def episode_num(self, node):
system = _getxmlattr(node, u'system')
if system == '':
system = 'onscreen'
return (node.textof(), system)
def video(self, node):
result = {}
for child in node.children:
result[child.name] = self._call(child)
return result
def audio(self, node):
result = {}
for child in node.children:
result[child.name] = self._call(child)
return result
def previously_shown(self, node):
data = {}
for attr in (u'start', u'channel'):
if node.attrs.has_key(('', attr)):
data[attr] = node.attrs[('', attr)]
return data
def premiere(self, node):
return _readwithlang(node)
def last_chance(self, node):
return _readwithlang(node)
def new(self, node):
return 1
def subtitles(self, node):
data = {}
if node.attrs.has_key(('', u'type')):
data['type'] = _getxmlattr(node, u'type')
for child in node.children:
if child.name == u'language':
data['language'] = _readwithlang(child)
return data
def rating(self, node):
data = {}
data['icon'] = []
if node.attrs.has_key(('', u'system')):
data['system'] = node.attrs[('', u'system')]
for child in node.children:
if child.name == u'value':
data['value'] = child.textof()
elif child.name == u'icon':
data['icon'].append(self.icon(child))
if len(data['icon']) == 0:
del data['icon']
return data
def star_rating(self, node):
data = {}
data['icon'] = []
for child in node.children:
if child.name == u'value':
data['value'] = child.textof()
elif child.name == u'icon':
data['icon'].append(self.icon(child))
if len(data['icon']) == 0:
del data['icon']
return data
#
# <credits> sub-tags
#
def actor(self, node):
return node.textof()
def director(self, node):
return node.textof()
def writer(self, node):
return node.textof()
def adapter(self, node):
return node.textof()
def producer(self, node):
return node.textof()
def presenter(self, node):
return node.textof()
def commentator(self, node):
return node.textof()
def guest(self, node):
return node.textof()
#
# <video> and <audio> sub-tags
#
def present(self, node):
return _decodeboolean(node)
def colour(self, node):
return _decodeboolean(node)
def aspect(self, node):
return node.textof()
def stereo(self, node):
return node.textof()
#
# Magic
#
def _call(self, node):
try:
return getattr(self, string.replace(node.name.encode(), '-', '_'))(node)
except NameError:
return '**Unhandled Element**'
except AttributeError:
return '**Unhandled Element**'
class _ChannelHandler:
"""
Handles XMLTV channel elements
"""
def display_name(self, node):
return _readwithlang(node)
def icon(self, node):
data = {}
for attr in (u'src', u'width', u'height'):
if node.attrs.has_key(('', attr)):
data[attr] = _getxmlattr(node, attr)
return data
def url(self, node):
return node.textof()
#
# More Magic
#
def _call(self, node):
try:
return getattr(self, string.replace(node.name.encode(), '-', '_'))(node)
except NameError:
return '**Unhandled Element**'
#
# Some convenience functions, treat them as private
#
def _extractNodes(node, handler):
"""
Builds a dictionary from the sub-elements of node.
'handler' should be an instance of a handler class
"""
result = {}
for child in node.children:
if not result.has_key(child.name):
result[child.name] = []
result[child.name].append(handler._call(child))
return result
def _getxmlattr(node, attr):
"""
If 'attr' is present in 'node', return the value, else return an empty
string
Yeah, yeah, namespaces are ignored and all that stuff
"""
if node.attrs.has_key((u'', attr)):
return node.attrs[(u'', attr)]
else:
return u''
def _readwithlang(node):
"""
Returns a tuple containing the text of a 'node' and the text of the 'lang'
attribute
"""
return (node.textof(), _getxmlattr(node, u'lang'))
def _decodeboolean(node):
text = node.textof().lower()
if text == 'yes':
return 1
elif text == 'no':
return 0
else:
return None
def _node_to_programme(node):
"""
Create a programme dictionary from a qp_xml 'node'
"""
handler = _ProgrammeHandler()
programme = _extractNodes(node, handler)
for attr in (u'start', u'channel'):
programme[attr] = node.attrs[(u'', attr)]
if (u'', u'stop') in node.attrs:
programme[u'stop'] = node.attrs[(u'', u'stop')]
#else:
# Sigh. Make show zero-length. This will allow the show to appear in
# searches, but it won't be seen in a grid, if the grid is drawn to
# scale
#programme[u'stop'] = node.attrs[(u'', u'start')]
return programme
def _node_to_channel(node):
"""
Create a channel dictionary from a qp_xml 'node'
"""
handler = _ChannelHandler()
channel = _extractNodes(node, handler)
channel['id'] = node.attrs[('', 'id')]
return channel
def read_programmes(fp):
"""
Read an XMLTV file and get out the relevant information for each
programme.
Parameter: file object to read from
Returns: list of hashes with start, titles, etc.
"""
parser = qp_xml.Parser()
doc = parser.parse(fp.read())
programmes = []
for node in doc.children:
if node.name == u'programme':
programmes.append(_node_to_programme(node))
return programmes
def read_data(fp):
"""
Get the source and other info from an XMLTV file.
Parameter: filename to read from
Returns: dictionary of <tv> attributes
"""
parser = qp_xml.Parser()
doc = parser.parse(fp.read())
attrs = {}
for key in doc.attrs.keys():
attrs[key[1]] = doc.attrs[key]
return attrs
def read_channels(fp):
"""
Read the channels.xml file and return a list of channel
information.
"""
parser = qp_xml.Parser()
doc = parser.parse(fp.read())
channels = []
for node in doc.children:
if node.name == u'channel':
channels.append(_node_to_channel(node))
return channels
class Writer:
"""
A class for generating XMLTV data
**All strings passed to this class must be Unicode, except for dictionary
keys**
"""
def __init__(self, fp, encoding="iso-8859-1", date=None,
source_info_url=None, source_info_name=None,
generator_info_url=None, generator_info_name=None):
"""
Arguments:
'fp' -- A File object to write XMLTV data to
'encoding' -- The text encoding that will be used.
*Defaults to 'iso-8859-1'*
'date' -- The date this data was generated. *Optional*
'source_info_url' -- A URL for information about the source of the
data. *Optional*
'source_info_name' -- A human readable description of
'source_info_url'. *Optional*
'generator_info_url' -- A URL for information about the program
that is generating the XMLTV document.
*Optional*
'generator_info_name' -- A human readable description of
'generator_info_url'. *Optional*
"""
self.fp = fp
self.encoding = encoding
self.date = date
self.source_info_url = source_info_url
self.source_info_name = source_info_name
self.generator_info_url = generator_info_url
self.generator_info_name = generator_info_name
s = """<?xml version="1.0" encoding="%s"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
""" % self.encoding
# tv start tag
s += "<tv"
for attr in ('date', 'source_info_url', 'source_info_name',
'generator_info_url', 'generator_info_name'):
if attr:
s += ' %s="%s"' % (attr, self.__dict__[attr])
s += ">\n"
self.fp.write(s)
def _validateStructure(self, d):
"""
Raises 'TypeError' if any strings are not Unicode
Argumets:
's' -- A dictionary
"""
if type(d) == types.StringType:
raise TypeError ('All strings, except keys, must be in Unicode. Bad
string: %s' % d)
elif type(d) == types.DictType:
for key in d.keys():
self._validateStructure(d[key])
elif type(d) == types.TupleType or type(d) == types.ListType:
for i in d:
self._validateStructure(i)
def _formatCDATA(self, cdata):
"""
Returns fixed and encoded CDATA
Arguments:
'cdata' -- CDATA you wish to encode
"""
# Let's do what 4Suite does, and replace bad characters with '?'
cdata = XML_BADCHARS.sub(u'?', cdata)
return saxutils.escape(cdata).encode(self.encoding)
def _formatTag(self, tagname, attrs=None, pcdata=None, indent=4):
"""
Return a simple tag
Arguments:
'tagname' -- Name of tag
'attrs' -- dictionary of attributes
'pcdata' -- Content
'indent' -- Number of spaces to indent
"""
s = indent*' '
s += '<%s' % tagname
if attrs:
for key in attrs.keys():
s += ' %s="%s"' % (key, self._formatCDATA(attrs[key]))
if pcdata:
s += '>%s</%s>\n' % (self._formatCDATA(pcdata), tagname)
else:
s += '/>\n'
return s
def end(self):
"""
Write the end of an XMLTV document
"""
self.fp.write("</tv>\n")
def write_programme(self, programme):
"""
Write a single XMLTV 'programme'
Arguments:
'programme' -- A dict representing XMLTV data
"""
self._validateStructure(programme)
s = ' <programme'
# programme attributes
for attr in ('start', 'channel'):
if programme.has_key(attr):
s += ' %s="%s"' % (attr, self._formatCDATA(programme[attr]))
else:
raise ValueError("'programme' must contain '%s' attribute" % attr)
for attr in ('stop', 'pdc-start', 'vps-start', 'showview', 'videoplus',
'clumpidx'):
if programme.has_key(attr):
s += ' %s="%s"' % (attr, self._formatCDATA(programme[attr]))
s += '>\n'
# Required children
err = 0
if programme.has_key('title'):
if len(programme['title']) > 0:
for title in programme['title']:
if title[1] != u'':
attrs = {'lang': title[1]}
else:
attrs=None
s += self._formatTag('title', attrs, title[0])
else:
err = 1
else:
err = 1
if err:
raise ValueError("'programme' must contain at least one 'title' element")
# Zero or more children with PCDATA and 'lang' attribute
for element in ('sub-title', 'desc', 'category', 'country'):
if programme.has_key(element):
for item in programme[element]:
if item[1] != u'':
attrs = {'lang': item[1]}
else:
attrs=None
s += self._formatTag(element, attrs, item[0])
# Zero or one children with PCDATA and 'lang' attribute
for element in ('language', 'orig-language', 'premiere', 'last-chance'):
if programme.has_key(element):
if len(programme[element]) != 1:
raise ValueError("Only one '%s' element allowed" % element)
if programme[element][0][1] != u'':
attrs = {'lang': programme[element][0][1]}
else:
attrs=None
s += self._formatTag(element, attrs, programme[element][0][0])
# Credits
if programme.has_key('credits'):
s += ' <credits>\n'
for credit in ('director', 'actor', 'writer', 'adapter',
'producer', 'presenter', 'commentator', 'guest'):
if programme['credits'][0].has_key(credit):
for name in programme['credits'][0][credit]:
s += self._formatTag(credit, pcdata=name, indent=6)
s += ' </credits>\n'
# Date
if programme.has_key('date'):
if len(programme['date']) != 1:
raise ValueError("Only one 'date' element allowed")
s += self._formatTag('date', pcdata=programme['date'][0])
# Length
if programme.has_key('length'):
if len(programme['length']) != 1:
raise ValueError("Only one 'length' element allowed")
s += self._formatTag('length', {'units': programme['length'][0]['units']},
str(programme['length'][0]['length']).decode(self.encoding))
# Icon
if programme.has_key('icon'):
for icon in programme['icon']:
if icon.has_key('src'):
s += self._formatTag('icon', icon)
else:
raise ValueError("'icon' element requires 'src' attribute")
# URL
if programme.has_key('url'):
for url in programme['url']:
s += self._formatTag('url', pcdata=url)
# Episode-num
if programme.has_key('episode-num'):
if len(programme['episode-num']) != 1:
raise ValueError("Only one 'episode-num' element allowed")
s += self._formatTag('episode-num', {'system':
programme['episode-num'][0][1]},
programme['episode-num'][0][0])
# Video and audio details
for element in ('video', 'audio'):
if programme.has_key(element):
s += ' <%s>\n' % element
for key in programme[element][0]:
s += self._formatTag(key,
pcdata=str(programme[element][0][key]).decode(self.encoding), indent=6)
s += ' </%s>\n' % element
# Previously shown
if programme.has_key('previously-shown'):
s += self._formatTag('previously-shown', programme['previously-shown'][0])
# New
if programme.has_key('new'):
s += self._formatTag('new')
# Subtitles
if programme.has_key('subtitles'):
s += ' <subtitles'
if programme['subtitles'][0].has_key('type'):
s += ' type="%s"' %
self._formatCDATA(programme['subtitles'][0]['type'])
s += '>\n'
if programme['subtitles'][0].has_key('language'):
if programme['subtitles'][0]['language'][1] != u'':
attrs = {'lang': programme['subtitles'][0]['language'][1]}
else:
attrs = None
s += self._formatTag('language', None,
programme['subtitles'][0]['language'][0], indent=6)
s += ' </subtitles>\n'
# Rating and star rating
for element in ('rating', 'star-rating'):
if programme.has_key(element):
s += ' <%s' % element
if element == 'rating':
if programme[element][0].has_key('system'):
s += ' system="%s"' %
self._formatCDATA(programme[element][0]['system'])
s += '>\n'
if programme[element][0].has_key('value'):
s += self._formatTag('value',
pcdata=programme[element][0]['value'], indent=6)
if programme[element][0].has_key('icon'):
for icon in programme[element][0]['icon']:
s += self._formatTag('icon', icon, indent=6)
s += ' </%s>\n' % element
# End tag
s += ' </programme>\n'
self.fp.write(s)
def write_channel(self, channel):
"""
Write a single XMLTV 'channel'
Arguments:
'channel' -- A dict representing XMLTV data
"""
self._validateStructure(channel)
s = ' <channel id="%s">\n' % channel['id']
# Write display-name(s)
err = 0
if channel.has_key('display-name'):
if len(channel['display-name']) > 0:
for name in channel['display-name']:
if name[1] != u'':
attrs = {'lang': name[1]}
else:
attrs = None
s += self._formatTag('display-name', attrs, name[0])
else:
err = 1
else:
err = 1
if err:
raise ValueError("'channel' must contain at least one 'display-name'
element")
# Icon
if channel.has_key('icon'):
for icon in channel['icon']:
if icon.has_key('src'):
s += self._formatTag('icon', icon)
else:
raise ValueError("'icon' element requires 'src' attribute")
# URL
if channel.has_key('url'):
for url in channel['url']:
s += self._formatTag('url', pcdata=url)
s += ' </channel>\n'
self.fp.write(s)
if __name__ == '__main__':
# Tests
from pprint import pprint
from StringIO import StringIO
import sys
# An example file
xmldata = StringIO("""<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv date="20030811003608 -0300"
source-info-url="http://www.funktronics.ca/python-xmltv"
source-info-name="Funktronics" generator-info-name="python-xmltv"
generator-info-url="http://www.funktronics.ca/python-xmltv">
<channel id="C10eltv.zap2it.com">
<display-name>Channel 10 ELTV</display-name>
<url>http://www.eastlink.ca/</url>
</channel>
<channel id="C11cbht.zap2it.com">
<display-name lang="en">Channel 11 CBHT</display-name>
<icon src="http://tvlistings2.zap2it.com/tms_network_logos/cbc.gif"/>
</channel>
<programme start="20030702000000 ADT" channel="C23robtv.zap2it.com"
stop="20030702003000 ADT">
<title>This Week in Business</title>
<category>Biz</category>
<category>Fin</category>
<date>2003</date>
<audio>
<stereo>stereo</stereo>
</audio>
</programme>
<programme start="20030702000000 ADT" channel="C36wuhf.zap2it.com"
stop="20030702003000 ADT">
<title>Seinfeld</title>
<sub-title>The Engagement</sub-title>
<desc>In an effort to grow up, George proposes marriage to former girlfriend
Susan.</desc>
<category>Comedy</category>
<country>USA</country>
<language>English</language>
<orig-language>English</orig-language>
<premiere lang="en">Not really. Just testing</premiere>
<last-chance>Hah!</last-chance>
<credits>
<actor>Jerry Seinfeld</actor>
<producer>Larry David</producer>
</credits>
<date>1995</date>
<length units="minutes">22</length>
<episode-num system="xmltv_ns">7 . 1 . 1/1</episode-num>
<video>
<colour>1</colour>
<present>1</present>
<aspect>4:3</aspect>
</video>
<audio>
<stereo>stereo</stereo>
</audio>
<previously-shown start="19950921103000 ADT" channel="C12whdh.zap2it.com"/>
<new/>
<subtitles type="teletext">
<language>English</language>
</subtitles>
<rating system="VCHIP">
<value>PG</value>
<icon src="http://some.ratings/PGicon.png" width="64" height="64"/>
</rating>
<star-rating>
<value>4/5</value>
<icon src="http://some.star/icon.png" width="32" height="32"/>
</star-rating>
</programme>
</tv>
""")
pprint(read_data(xmldata))
xmldata.seek(0)
pprint(read_channels(xmldata))
xmldata.seek(0)
pprint(read_programmes(xmldata))
# Test the writer
programmes = [{'audio': [{'stereo': u'stereo'}],
'category': [(u'Biz', u''), (u'Fin', u'')],
'channel': u'C23robtv.zap2it.com',
'date': [u'2003'],
'start': u'20030702000000 ADT',
'stop': u'20030702003000 ADT',
'title': [(u'This Week in Business', u'')]},
{'audio': [{'stereo': u'stereo'}],
'category': [(u'Comedy', u'')],
'channel': u'C36wuhf.zap2it.com',
'country': [(u'USA', u'')],
'credits': [{'producer': [u'Larry David'], 'actor': [u'Jerry
Seinfeld']}],
'date': [u'1995'],
'desc': [(u'In an effort to grow up, George proposes marriage to
former girlfriend Susan.',
u'')],
'episode-num': [(u'7 . 1 . 1/1', u'xmltv_ns')],
'language': [(u'English', u'')],
'last-chance': [(u'Hah!', u'')],
'length': [{'units': u'minutes', 'length': 22}],
'new': [1],
'orig-language': [(u'English', u'')],
'premiere': [(u'Not really. Just testing', u'en')],
'previously-shown': [{'channel': u'C12whdh.zap2it.com',
'start': u'19950921103000 ADT'}],
'rating': [{'icon': [{'height': u'64',
'src': u'http://some.ratings/PGicon.png',
'width': u'64'}],
'system': u'VCHIP',
'value': u'PG'}],
'star-rating': [{'icon': [{'height': u'32',
'src': u'http://some.star/icon.png',
'width': u'32'}],
'value': u'4/5'}],
'start': u'20030702000000 ADT',
'stop': u'20030702003000 ADT',
'sub-title': [(u'The Engagement', u'')],
'subtitles': [{'type': u'teletext', 'language': (u'English', u'')}],
'title': [(u'Seinfeld', u'')],
'video': [{'colour': 1, 'aspect': u'4:3', 'present': 1}]}]
channels = [{'display-name': [(u'Channel 10 ELTV', u'')],
'id': u'C10eltv.zap2it.com',
'url': [u'http://www.eastlink.ca/']},
{'display-name': [(u'Channel 11 CBHT', u'en')],
'icon': [{'src':
u'http://tvlistings2.zap2it.com/tms_network_logos/cbc.gif'}],
'id': u'C11cbht.zap2it.com'}]
w = Writer(sys.stdout, encoding="iso-8859-1",
date="20030811003608 -0300",
source_info_url="http://www.funktronics.ca/python-xmltv",
source_info_name="Funktronics",
generator_info_name="python-xmltv",
generator_info_url="http://www.funktronics.ca/python-xmltv")
for c in channels:
w.write_channel(c)
for p in programmes:
w.write_programme(p)
w.end()
Index: xmltv.py
===================================================================
RCS file: /cvsroot/freevo/freevo/lib/pyepg/xmltv.py,v
retrieving revision 1.1
retrieving revision 1.2
diff -C2 -d -r1.1 -r1.2
*** xmltv.py 26 Jul 2004 12:45:56 -0000 1.1
--- xmltv.py 10 Aug 2004 19:35:45 -0000 1.2
***************
*** 1,862 ****
#
! # xmltv.py - Python interface to XMLTV format, based on XMLTV.pm
! #
! # Copyright (C) 2001 James Oakley
! #
! # This library is free software; you can redistribute it and/or
! # modify it under the terms of the GNU Lesser General Public
! # License as published by the Free Software Foundation; either
! # version 2.1 of the License, or (at your option) any later version.
! #
[...1079 lines suppressed...]
! adj_secs = int(tz[1:3])*3600+ min*60
! if adj_neg:
! secs -= adj_secs
else:
! secs += adj_secs
! else:
! # No, use the regular conversion
+ ## WARNING! BUG HERE!
+ # The line below is incorrect; the strptime.strptime function doesn't
+ # handle time zones. There is no obvious function that does. Therefore
+ # this bug is left in for someone else to solve.
! try:
! secs = time.mktime(strptime.strptime(timestr, xmltv.date_format))
! except ValueError:
! timestr = timestr.replace('EST', '')
! secs = time.mktime(strptime.strptime(timestr, xmltv.date_format))
! return secs
Index: __init__.py
===================================================================
RCS file: /cvsroot/freevo/freevo/lib/pyepg/__init__.py,v
retrieving revision 1.2
retrieving revision 1.3
diff -C2 -d -r1.2 -r1.3
*** __init__.py 5 Aug 2004 17:16:05 -0000 1.2
--- __init__.py 10 Aug 2004 19:35:44 -0000 1.3
***************
*** 1,9 ****
import compat
! import epg_xmltv
import stat
import os
! def load(data = '/tmp/TV.xml'):
! return epg_xmltv.load_guide(data, verbose=True)
def timestamp(data = '/tmp/TV.xml'):
--- 1,9 ----
import compat
! import guide
import stat
import os
! def load(datafile, cachefile=None, parser='xmltv'):
! return guide.TvGuide(datafile, cachefile, parser, verbose=True)
def timestamp(data = '/tmp/TV.xml'):
--- epg_types.py DELETED ---
--- NEW FILE: guide.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# guide.py - basic guide class
# -----------------------------------------------------------------------
# $Id: guide.py,v 1.1 2004/08/10 19:35:45 dischi Exp $
#
# Notes:
# Todo:
#
# -----------------------------------------------------------------------
# $Log: guide.py,v $
# Revision 1.1 2004/08/10 19:35:45 dischi
# o better index generation
# o split into different files
# o support for other parsers than xmltv
# o internal caching
#
#
# -----------------------------------------------------------------------
# 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 os
import sys
import stat
import cPickle
import pickle
import config
if float(sys.version[0:3]) < 2.3:
PICKLE_PROTOCOL = 1
else:
PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL
# Internal version number for caching
EPG_VERSION = 0.1
class TvGuide:
"""
The complete TV guide with all channels
"""
def __init__(self, datafile, cachefile, parser, verbose):
# These two types map to the same channel objects
self.chan_dict = {} # Channels mapped using the id
self.chan_list = [] # Channels, ordered
try:
if os.stat(cachefile)[stat.ST_MTIME] < os.stat(datafile)[stat.ST_MTIME]:
raise IOError
f = open(cachefile)
try:
data = cPickle.load(f)
except:
data = pickle.load(f)
f.close()
if data[0] != EPG_VERSION:
raise IOError
_debug_('using epg cachefile %s' % cachefile)
self.chan_list = data[1]
for c in self.chan_list:
self.chan_dict[c.id] = c
except Exception, e:
exec('import %s' % parser)
getattr(eval(parser), 'load_guide')(self, datafile, verbose=verbose)
self.sort()
self.create_index()
if cachefile:
try:
if os.path.isfile(cachefile):
os.unlink(cachefile)
f = open(cachefile, 'w')
cPickle.dump((EPG_VERSION, self.chan_list), f, PICKLE_PROTOCOL)
f.close()
except IOError:
print 'unable to save to cachefile %s' % file
def add_channel(self, channel):
"""
add a channel to the list
"""
if not self.chan_dict.has_key(channel.id):
# Add the channel to both the dictionary and the list. This works
# well in Python since they will both point to the same object!
self.chan_dict[channel.id] = channel
self.chan_list.append(channel)
def add_program(self, program):
"""
add a program
the channel must be present, or the program is silently dropped
"""
if self.chan_dict.has_key(program.channel_id):
p = self.chan_dict[program.channel_id].programs
if len(p) and p[-1].start < program.stop and p[-1].stop > program.start:
# the tv guide is corrupt, the last entry has a stop time higher than
# the next start time. Correct that by reducing the stop time of
# the last entry
if config.DEBUG > 1:
print 'wrong stop time: %s' % \
String(self.chan_dict[program.channel_id].programs[-1])
self.chan_dict[program.channel_id].programs[-1].stop = program.start
if len(p) and p[-1].start == p[-1].stop:
# Oops, something is broken here
self.chan_dict[program.channel_id].programs = p[:-1]
self.chan_dict[program.channel_id].programs.append(program)
def sort(self):
"""
Sort all channel programs in time order
"""
for chan in self.chan_list:
chan.sort()
def __unicode__(self):
"""
return as unicode for debug
"""
s = u'XML TV Guide\n'
for chan in self.chan_list:
s += String(chan)
return s
def __str__(self):
"""
return as string for debug
"""
return String(self.__unicode__())
def create_index(self):
"""
create an index for faster access
"""
for chan in self.chan_list:
chan.create_index()
def get(self, id):
"""
return channel with given id
"""
try:
return self.chan_dict[id]
except IndexError:
None
--- epg_xmltv.py DELETED ---
--- NEW FILE: channel.py ---
# -*- coding: iso-8859-1 -*-
# -----------------------------------------------------------------------
# epg_types.py - This file contains the types for the Freevo Electronic
# Program Guide module.
# -----------------------------------------------------------------------
# $Id: channel.py,v 1.1 2004/08/10 19:35:45 dischi Exp $
#
# Notes:
# Todo:
#
# -----------------------------------------------------------------------
# $Log: channel.py,v $
# Revision 1.1 2004/08/10 19:35:45 dischi
# o better index generation
# o split into different files
# o support for other parsers than xmltv
# o internal caching
#
# Revision 1.3 2004/08/09 21:19:46 dischi
# make tv guide working again (but very buggy)
#
# Revision 1.2 2004/08/05 17:16:05 dischi
# misc enhancements
#
# Revision 1.20 2004/07/10 12:33:41 dischi
# header cleanup
#
# Revision 1.19 2004/07/01 22:49:49 rshortt
# Unicode fix.
#
# Revision 1.18 2004/06/22 01:07:49 rshortt
# Move stuff into __init__() and fix a bug for twisted's serialization.
#
# Revision 1.17 2004/03/05 20:49:11 rshortt
# Add support for searching by movies only. This uses the date field in xmltv
# which is what tv_imdb uses and is really acurate. I added a date property
# to TvProgram for this and updated findMatches in the record_client and
# recordserver.
#
# -----------------------------------------------------------------------
# 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 time
import config
from program import TvProgram
NO_DATA = _('no data')
class TvChannel:
"""
Information about one channel, holding all programs
"""
def __init__(self):
self.id = ''
self.displayname = ''
self.tunerid = ''
self.logo = ''
self.programs = []
self.index = {}
self.index_start = 0
self.index_end = 0
def sort(self):
"""
Sort the programs so that the earliest is first in the list
"""
sort_function = lambda a, b: cmp(a.start, b.start)
self.programs.sort(sort_function)
dummy_programs = [ TvProgram(NO_DATA, 0, self.programs[0].start),
TvProgram(NO_DATA, self.programs[-1].stop, 2147483647) ]
for i in range(len(self.programs)-1):
if self.programs[i].stop + 60 < self.programs[i+1].start:
# there is a hole in the data, fill it with dummy program
dummy_programs.append(TvProgram(NO_DATA, self.programs[i].stop,
self.programs[i+1].start))
self.programs += dummy_programs
self.programs.sort(sort_function)
def __unicode__(self):
"""
return as unicode for debug
"""
s = u'CHANNEL ID %-20s' % self.id
if self.programs:
s += u'\n'
for program in self.programs:
s += u' ' + unicode(program) + u'\n'
else:
s += u' NO DATA\n'
return s
def __str__(self):
"""
return as string for debug
"""
return String(self.__unicode__())
def create_index(self):
"""
create index for faster access
"""
last = 0
index = 0
last_key = 0
self.programs[0].index = 0
for p in self.programs[1:]:
index += 1
p.index = index
key = int(p.start) / (60 * 60 * 24)
if not self.index_start:
self.index_start = key
self.index_end = key
if not self.index.has_key(key):
if last_key:
while len(self.index[last_key]) < 48:
self.index[last_key].append(last)
self.index[key] = []
pos = (int(p.start) / (60 * 30)) % (48)
p.pos = pos
if len(self.index[key]) >= pos + 1:
last = index
continue
while len(self.index[key]) < pos:
self.index[key].append(last)
self.index[key].append(index)
last = index
last_key = key
while len(self.index[key]) < 48:
self.index[key].append(index)
def __get_pos__(self, start, stop):
"""
get internal positions for programs between start and stop
"""
start -= 60 * 30
key = int(start) / (60 * 60 * 24)
if key < self.index_start:
key = self.index_start
pos = 0
else:
pos = (int(start) / (60 * 30)) % (48)
start = max(self.index[key][pos], 0)
key = int(stop) / (60 * 60 * 24)
if key > self.index_end:
key = self.index_end
pos = 47
else:
pos = (int(stop) / (60 * 30)) % (48) + 1
if pos >= 48:
# next day
key += 1
if key > self.index_end:
key = self.index_end
pos = 47
else:
pos = 0
stop = self.index[key][pos] + 1
return start, stop
def get(self, start, stop=0):
"""
get programs between start and stop time or if stop=0, get
the program running at 'start'
"""
if not stop:
stop = start
start_p, stop_p = self.__get_pos__(start, stop)
f = lambda p, a=start, b=stop: not (p.start > b or p.stop < a)
try:
return filter(f, self.programs[start_p:stop_p])
except Exception, e:
return []
def next(self, prog):
"""
return next program after 'prog'
"""
pos = min(len(self.programs)-1, prog.index + 1)
prog = self.programs[pos]
if pos < len(self.programs) and not prog.valid:
return self.next(prog)
return prog
def prev(self, prog):
"""
return previous program before 'prog'
"""
pos = max(0, prog.index - 1)
prog = self.programs[pos]
if pos > 0 and not prog.valid:
return self.prev(prog)
return prog
-------------------------------------------------------
SF.Net email is sponsored by Shop4tech.com-Lowest price on Blank Media
100pk Sonic DVD-R 4x for only $29 -100pk Sonic DVD+R for only $33
Save 50% off Retail on Ink & Toner - Free Shipping and Free Gift.
http://www.shop4tech.com/z/Inkjet_Cartridges/9_108_r285
_______________________________________________
Freevo-cvslog mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/freevo-cvslog