#!/usr/bin/env python
# -----------------------------------------------------------------------
# Copyright (C) 2003 Chris Ottrey.
#
# 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.
# -----------------------------------------------------------------------
#
# This code is part of the pytvgrab project:
# http://pytvgrab.sourceforge.net
#
# -----------------------------------------------------------------------
# Subversion Information, do not edit
#
# $Rev: 258 $
# $LastChangedDate: 2004-11-13 16:45:05 +1100 (Sat, 13 Nov 2004) $
# $LastChangedRevision: 258 $
# $LastChangedBy: ottrey $
#
# $Log: $
#
import sys
import string
import time
import copy
import message
import i18n
OUTPUT_ENCODING = 'UTF-8'
TOFFSET_MINUTES = 0
def _underscore_2_dash(str):
result=string.lower(str)
result=string.replace(result, '_', '-')
return result
def setOutputEncoding(enc):
""" Will set the OUTPUT_ENCODING global variable. """
global OUTPUT_ENCODING
OUTPUT_ENCODING = enc
# Test the encoding
unicode("abc",OUTPUT_ENCODING).encode(OUTPUT_ENCODING)
# setOutputEncoding
class _Element:
TABSTOP=2*' '
attributes=[]
def __init__(self, **kwargs):
self.pcdata = ''
self.elements={}
self.__dict__.update(kwargs)
# __init__()
def setPcdata(self, pcdata):
self.pcdata=pcdata
# setPcdata()
def getName(self):
result=_underscore_2_dash(self.__class__.__name__)
return result
# getName()
def addElement(self, element):
name=element.getName()
if self.elements.has_key(name):
self.elements[name].append(element)
else:
self.elements[name]=[element]
# addElement()
def _elementsHasDataSpec(self):
"""
Finds out if the data has some data type specification
(lang,system and such..)
"""
if self.pcdata:
if isinstance(self.pcdata, XMLDataAndAttr):
return True
#if type[self.pcdata] == type([]) and \
# len(self.pcdata) > 0 and \
# isinstance(self.pcdata[0], XMLDataAndAttr):
# return True
return False
def _singleElementStr(self,element,indent):
result=''
if self.elements.has_key(element):
obj=self.elements[element]
if obj:
for i in obj:
result=result+'%s' % i.__str__(indent+self.TABSTOP)
return result
def _elementsStr(self, indent=''):
result=''
#if 'pcdata' in self.elementList:
if self.pcdata:
if isinstance( self.pcdata, unicode ):
self.pcdata = self.pcdata.encode( OUTPUT_ENCODING )
result=result+'%s' % self.pcdata
elif self._elementsHasDataSpec():
data = self.pcdata.data
spec = self.pcdata.spec
if isinstance( data, unicode ):
data = data.encode( OUTPUT_ENCODING, 'replace')
if isinstance( spec, unicode ):
spec = spec.encode( OUTPUT_ENCODING, 'replace')
result=result+'%s>%s' % (spec,data)
else:
result=result+'%s' % self.pcdata
else:
result=result+'\n'
if hasattr( self, 'elementOrder' ):
for element in self.elementOrder:
if self.elements.has_key(element):
#if element in self.elementList:
result+= self._singleElementStr(element,indent)
# do the elements that's NOT in the element order last
for element in self.elementList:
if not (element in self.elementOrder):
result+= self._singleElementStr(element,indent)
else:
for element in self.elementList:
result+= self._singleElementStr(element,indent)
result=result+indent
return result
# _elementsStr()
def __str__(self, indent=''):
name=self.getName()
result='%s<%s ' % (indent, name)
for attribute in self.attributes:
if self.__dict__.has_key(attribute):
value=self.__dict__[attribute]
if value:
if isinstance( value, unicode ):
value = value.encode( OUTPUT_ENCODING )
result=result+'%s="%s" ' % (_underscore_2_dash(attribute), value)
if not self._elementsHasDataSpec():
# Don't add the closing '>' to the tag, _elementsStr will do that
result=result[:-1]+'>'
result=result+self._elementsStr(indent)
result=result+'</%s>\n' % (name)
return result
# __str__()
# _Element
class Element(_Element):
""" Element
NB. This class does not enforce any order,
frequency or combinations of its child elements.
(I was thinking maybe I could parse the DTD for this.
Hmmm.. I'd have to do that _every_ time it was used. )
"""
def __init__(self, name, **attributes):
self.elements = {}
self.elementList = []
self.attributes = []
self.pcdata = ''
self.name = name
if attributes.has_key('attributes'):
attributes=attributes['attributes']
for attr, value in attributes.items():
if value:
self.attributes.append(attr)
self.__dict__[attr]=value
# __init__()
def addElement(self, element):
name=element.getName()
if self.elements.has_key(name):
self.elements[name].append(element)
else:
self.elements[name]=[element]
if name not in self.elementList:
self.elementList.append(name)
# addElement()
def getName(self):
return self.name
# getName()
# Element
class XMLDataAndAttr:
"""
This class is used as a container for those fields
where data and a data specifying attribute is needed.
i.e. XMLDataAndAttr("A title", 'lang="sv"')
=> <title lang="sv">A title</title>
"""
def __init__(self, data, spec):
self.data = data
self.spec = spec
def __str__(self):
data = self.data
if isinstance( data, unicode ):
data = data.encode( OUTPUT_ENCODING )
return data
def __repr__(self):
if isinstance( self.data, unicode ):
return self.data.encode('ascii', 'replace')
return self.data
# XMLDataAndAttr
class XMLFile:
def __init__(self, root, dtd):
self.root=root
self.dtd=dtd
# __init__()
def __str__(self):
result=''
result=result+'<?xml version="1.0" encoding="'+ OUTPUT_ENCODING + '"?>\n'
result=result+'<!DOCTYPE %s SYSTEM "%s">\n\n' % (self.root.getName(), self.dtd)
result=result+'%s' % self.root
return result
# __str__()
# XMLFile
class XMLTV(XMLFile):
def __init__(self, generator_info_name, root='tv', dtd='xmltv.dtd'):
self.tv=Element('tv', generator_info_name=generator_info_name)
self.root=self.tv
self.dtd=dtd
self.channel={}
self.program={}
# __init__()
def addChannel(self, id, display_name, icon=None, url=None):
if id not in self.channel.keys():
message.moreinfo(_('Adding channel %s to xmltv') % repr(id))
c=Element('channel', id=id)
d=Display_Name()
d.setPcdata(display_name)
if icon:
i=Element('icon', attributes=icon)
c.addElement(i)
if url:
u=Element('url')
u.setPcdata(url)
c.addElement(u)
c.addElement(d)
self.tv.addElement(c)
self.channel.update({id: c})
self.program.update({id: []})
# addChannel()
def addProgram(self, start, channel, title, stop=None, info=None):
message.moreinfo(_('Adding program %s to channel %s') % (repr(title), repr(channel)))
# this should be done in grabbers
# take a look at br_uol, it does using 2 regexp
# (maybe there is a better way, if so, let me know -- Gustavo)
#title=title.replace('&', '&') # hmmm.. fussy fussy
p=Program('programme', start=start, stop=stop, channel=channel)
t=Element('title')
t.setPcdata(title)
p.addElement(t)
if info:
for name, data in info.items():
if type(data) == type([]) and \
len(data) > 0 and isinstance(data[0],XMLDataAndAttr):
# Special handling for arrays of XMLDataAndAttr
for ee in data:
e = Element(name)
e.setPcdata(ee)
p.addElement(e)
else:
e=Element(name)
if type(data) == type('') or isinstance(data, unicode)\
or isinstance(data,XMLDataAndAttr):
e.setPcdata(data)
elif type(data) == type({}):
for n2, d2 in data.items():
e2=Element(n2)
e2.setPcdata(d2)
e.addElement(e2)
elif type(data) == type([]):
for ee in data:
for n2, d2 in ee.items():
e2=Element(n2)
e2.setPcdata(d2)
e.addElement(e2)
p.addElement(e)
self.tv.addElement(p)
try:
self.program[channel].append(p)
except :
sys.stderr.write('[%s]\n' % channel)
sys.stderr.write('[%s]\n' % self.program.keys())
# addProgram()
def set_stops(self):
"""
Adds stops to programs that doesn't have a stop.
This is done by using the start time of the next programme
or 24hr is added to the start if this is the last grabbed program
"""
for c in self.channel.keys():
#sometimes there is no programs at all for a channel..
if len(self.program[c])==0:
continue
elif len(self.program[c])==1:
# prep prev_p for the last resort +24h
prev_p = prev_p=self.program[c][0]
else:
prev_p=self.program[c][0]
for p in self.program[c][1:]:
# Only set stop if it didn't already exist
if not (hasattr( prev_p, 'stop' ) and prev_p.stop ):
# Ugly hack here because 'stop' wasn't given to the Element constructor.
# Which means it won't be in the attributes list.
if not 'stop' in prev_p.attributes:
prev_p.attributes.append('stop')
prev_p.stop=p.start
# remove doubles
if prev_p.start == p.start:
self.program[c].remove(p)
prev_p=p
# for
# Last resort: if we could not find a stop
# (generally on the last program) we set the program duration
# to be 24 hours.
if not hasattr( prev_p, 'stop' ):
prev_p.stop = copy.copy( prev_p.start )
prev_p.stop.nextDay()
if not 'stop' in prev_p.attributes:
prev_p.attributes.append( "stop" )
# for
# set_stops()
def offset_times(self):
for c in self.channel.keys():
#sometimes there is no programs at all for a channel..
if len(self.program[c])==0:
continue
else:
for p in self.program[c]:
p.start=p.start+TOFFSET_MINUTES*60
if hasattr( p, 'stop' ):
p.stop=p.stop+TOFFSET_MINUTES*60
# offset_times()
def write(self, file=None):
# first do some house keeping
self.set_stops()
self.offset_times()
buf=str(self)
if buf:
if file:
f=open(file, 'w')
f.write('%s' % buf)
f.close()
else:
print buf
statistics={
'channels': len(self.root.elements.get('channel', [])) ,
'programs': len(self.root.elements.get('programme', [])) ,
'descriptions': 0 ,
}
message.moreinfo(_("Statistics (NOT WORKING): channels=%(channels)s, programs=%(programs)s, descriptions=%(descriptions)s") % statistics)
# write()
# XMLTV
# ---------- Start: this section will be deprecated -------------- #
# ---------- (some of) XMLTV DTD -----------------------------------#
# (version 0.5.35 backwards compatible with 0.5.15) starts here --- #
#
class Display_Name(_Element): elementList = ['pcdata']
class Channel (_Element):
elementList = ['display-name']
#elementList = 'display-name+'
attributes = ['id']
class Title (_Element): elementList = ['pcdata']
class Desc (_Element): elementList = ['pcdata']
class Director (_Element): elementList = ['pcdata']
class Actor (_Element): elementList = ['pcdata']
class Credits (_Element):
elementList = ['director', 'actor']
#elementList = 'director*, actor*'
class Colour (_Element): elementList = ['pcdata']
class Video (_Element):
elementList = ['colour']
#elementList = 'colour?'
class Value (_Element): elementList = ['pcdata']
class Rating (_Element):
elementList = ['value']
#elementList = 'value'
class Program (Element):
elementOrder = ['title', 'sub-title', 'desc', 'credits', 'date',\
'category', 'language', 'orig-language', 'length',\
'icon', 'url', 'country', 'episode-num', 'video', 'audio',\
'previously-shown', 'premiere', 'last-chance', 'new',\
'subtitles', 'rating', 'star-rating']
#elementList = 'title+, desc*, credits?, video?, rating*'
attributes = ['start', 'stop', 'channel']
class TV (_Element):
elementOrder = ['channel', 'programme']
#elementList = 'channel, programme'
elementList = ['generator_info_name']
# ---------- (some of) XMLTV DTD (version 0.5.35 - 0.5.15) ends here ----- #
# ---------- End: this section will be deprecated ---------------- #
# -------------- Unit Tests -------------- #
using_unittest2=False
try:
import unittest2 as unittest
using_unittest2=True
except:
import unittest
class XMLTV_UnitTest(unittest.TestCase):
def setUp(self):
message.verbose=message.verbose_level.ERROR
self.xmltv=XMLTV('AU python generator')
# "UTF-8" is default, this is same same but different :)
setOutputEncoding("utf-8")
self.xmltv.addChannel( 2 , 'ABC' , icon={'src':'http://abc.net.au/common/logos/whtblkwht.gif'}, url='http://abc.net.au')
self.xmltv.addChannel( 7 , 'Seven' )
self.xmltv.addChannel( 9 , 'Nine' )
self.xmltv.addProgram( '200307181900' , 2 , 'ABC News' )
info={}
info['rating'] = {'value':'G'}
info['desc'] = [XMLDataAndAttr('Description','lang="en"'),
XMLDataAndAttr(u'Beskrivning','lang="sv"'),
XMLDataAndAttr(u'De beschrijving','lang="nl"'),
XMLDataAndAttr('Beskrivelse','lang="no"'),
XMLDataAndAttr(u'Description, mate :)','lang="au"'),]
info['unsupported-element'] = "should go last (if anywhere)"
info['sub-title'] = "sub-title"
info['credits'] = "credits"
info['date'] = "date"
info['category'] = u"Sci-Fi"
info['language'] = u"fi"
info['orig-language'] = u"orig-language"
info['length'] = "length"
info['icon'] = u"icon"
info['url'] = u"url"
info['country'] = "country"
info['episode-num'] = XMLDataAndAttr('.1/6.','system="xmltv_ns"')
info['video'] = "video"
info['audio'] = "audio"
info['previously-shown'] = "previously-shown"
info['premiere'] = "premiere"
info['last-chance'] = "last-chance"
info['new'] = "new"
info['subtitles'] = "subtitles"
info['rating'] = "rating"
info['star-rating'] = "star-rating"
self.xmltv.addProgram( '200307182400' , 7 , XMLDataAndAttr('Home and Away','lang="au"'), info=info)
self.xmltv.addProgram( '200307182400' , 9 , 'Frasier' , info={'rating': {'value':'G'}} )
self.xmltv_str="""<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE tv SYSTEM "xmltv.dtd">
<tv generator-info-name="AU python generator">
<channel id="2">
<icon src="http://abc.net.au/common/logos/whtblkwht.gif">
</icon>
<url>http://abc.net.au</url>
<display-name>ABC</display-name>
</channel>
<channel id="7">
<display-name>Seven</display-name>
</channel>
<channel id="9">
<display-name>Nine</display-name>
</channel>
<programme start="200307181900" channel="2">
<title>ABC News</title>
</programme>
<programme start="200307182400" channel="7">
<title lang="au">Home and Away</title>
<sub-title>sub-title</sub-title>
<desc lang="en">Description</desc>
<desc lang="sv">Beskrivning</desc>
<desc lang="nl">De beschrijving</desc>
<desc lang="no">Beskrivelse</desc>
<desc lang="au">Description, mate :)</desc>
<credits>credits</credits>
<date>date</date>
<category>Sci-Fi</category>
<language>fi</language>
<orig-language>orig-language</orig-language>
<length>length</length>
<icon>icon</icon>
<url>url</url>
<country>country</country>
<episode-num system="xmltv_ns">.1/6.</episode-num>
<video>video</video>
<audio>audio</audio>
<previously-shown>previously-shown</previously-shown>
<premiere>premiere</premiere>
<last-chance>last-chance</last-chance>
<new>new</new>
<subtitles>subtitles</subtitles>
<rating>rating</rating>
<star-rating>star-rating</star-rating>
<unsupported-element>should go last (if anywhere)</unsupported-element>
</programme>
<programme start="200307182400" channel="9">
<title>Frasier</title>
<rating>
<value>G</value>
</rating>
</programme>
</tv>
"""
# setUp()
def test01(self): v=str(self.xmltv); assert v == self.xmltv_str, v
if using_unittest2 or __name__ == '__main__':
unittest.main()
# -------------- Unit Tests -------------- #
|