A Python UPnP Media Server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
5.8 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2008 John-Mark Gurney <jmg@funktaht.com>
  3. '''PVR Interface'''
  4. __version__ = '$Change: 1109 $'
  5. # $Id: //depot/python/pymeds/main/shoutcast.py#13 $
  6. from DIDLLite import Container, Item, VideoItem, Resource
  7. from FSStorage import registerklassfun, FSObject
  8. import os.path
  9. import time
  10. from twisted.internet import reactor
  11. from twisted.python import log
  12. import twisted.web
  13. import urlparse
  14. import urllib2
  15. def getPage(url, contextFactory=None, *args, **kwargs):
  16. """Download a web page as a string.
  17. Download a page. Return the HTTPClientFactory, which will
  18. callback with a page (as a string) or errback with a
  19. description of the error.
  20. See HTTPClientFactory to see what extra args can be passed.
  21. """
  22. from twisted.web.client import _parse, HTTPClientFactory
  23. scheme, host, port, path = _parse(url)
  24. factory = HTTPClientFactory(url, *args, **kwargs)
  25. if scheme == 'https':
  26. from twisted.internet import ssl
  27. if contextFactory is None:
  28. contextFactory = ssl.ClientContextFactory()
  29. reactor.connectSSL(host, port, factory, contextFactory)
  30. else:
  31. reactor.connectTCP(host, port, factory)
  32. return factory
  33. class PYVRShow(VideoItem):
  34. def __init__(self, *args, **kwargs):
  35. baseurl = kwargs['url']
  36. self.info = kwargs['info']
  37. del kwargs['info'], kwargs['url']
  38. VideoItem.__init__(self, *args, **kwargs)
  39. url = self.info['link']
  40. sc = urlparse.urlparse(url)[0]
  41. if not sc:
  42. # need to combine w/ base url
  43. url = urlparse.urljoin(baseurl, url)
  44. self.res = Resource(url,
  45. 'http-get:*:%s:*' % self.info['mimetype'])
  46. self.res.duration = self.info['duration']
  47. def doUpdate(self):
  48. pass
  49. import xml.sax
  50. import xml.sax.handler
  51. from xml.sax.saxutils import unescape
  52. class RecordingXML(xml.sax.handler.ContentHandler):
  53. dataels = ('title', 'subtitle', 'duration', 'mimetype', 'link',
  54. 'delete', )
  55. def __init__(self):
  56. self.shows = {}
  57. self.data = None
  58. def characters(self, chars):
  59. if self.data is not None:
  60. self.data.append(chars)
  61. def startElement(self, name, attrs):
  62. if name in self.dataels:
  63. self.data = []
  64. self.curel = name
  65. elif name == 'record':
  66. self.currec = {}
  67. def endElement(self, name):
  68. if name in self.dataels:
  69. data = unescape(''.join(self.data))
  70. self.currec[self.curel] = data
  71. elif name == 'record':
  72. rec = self.currec
  73. try:
  74. self.shows[rec['title']].append(rec)
  75. except KeyError:
  76. self.shows[rec['title']] = [ rec ]
  77. self.data = None
  78. def recxmltoobj(page):
  79. obj = RecordingXML()
  80. xml.sax.parseString(page, obj)
  81. return obj.shows
  82. class PYVRShows(Container):
  83. def __init__(self, *args, **kwargs):
  84. self.pyvr = kwargs['pyvr']
  85. del kwargs['pyvr']
  86. self.show = kwargs['show']
  87. del kwargs['show']
  88. Container.__init__(self, *args, **kwargs)
  89. self.shows = {}
  90. self.lastmodified = None
  91. def checkUpdate(self):
  92. self.pyvr.checkUpdate()
  93. if self.pyvr.lastmodified != self.lastmodified:
  94. self.doUpdate()
  95. @staticmethod
  96. def getunique(eps, ep):
  97. i = 1
  98. while True:
  99. title = '%s Copy %d' % (ep['subtitle'], i)
  100. if not eps.has_key(title):
  101. return title
  102. i += 1
  103. @staticmethod
  104. def eplisttodict(eps):
  105. ret = {}
  106. for pos, i in enumerate(eps):
  107. title = i['subtitle']
  108. if ret.has_key(title):
  109. print 'WARNING: dup:', `i`, `ret[title]`
  110. title = PYVRShows.getunique(ret, i)
  111. i['pos'] = pos
  112. ret[title] = i
  113. return ret
  114. def genChildren(self):
  115. return self.eplisttodict(self.pyvr.shows[self.show])
  116. def createObject(self, i, arg):
  117. return PYVRShow, i, (), { 'url': self.pyvr.url, 'info': arg }
  118. def sort(self):
  119. Container.sort(self, lambda x, y: cmp(x.info['pos'],
  120. y.info['pos']))
  121. def doUpdate(self):
  122. Container.doUpdate(self)
  123. self.lastmodified = self.pyvr.lastmodified
  124. class PYVR(FSObject, Container):
  125. def __init__(self, *args, **kwargs):
  126. self.url = kwargs.pop('url')
  127. FSObject.__init__(self, kwargs.pop('path'))
  128. Container.__init__(self, *args, **kwargs)
  129. #self.pend = None
  130. self.lastmodified = None
  131. self.newobjs = None
  132. self.objs = {}
  133. self.lastcheck = 0
  134. def checkUpdate(self):
  135. #if self.pend is not None:
  136. # raise self.pend
  137. if time.time() - self.lastcheck < 10:
  138. return
  139. # Check to see if any changes have been made
  140. self.runCheck()
  141. def runCheck(self):
  142. while True:
  143. try:
  144. self.page = urllib2.urlopen(self.url)
  145. break
  146. except urllib2.HTTPError:
  147. time.sleep(.1)
  148. #self.page = getPage(self.url, method='HEAD')
  149. #self.page.deferred.addErrback(self.errCheck).addCallback(
  150. # self.doCheck)
  151. #self.pend = self.page.deferred
  152. return self.doCheck(self.page)
  153. def errCheck(self, x):
  154. print 'errCheck:', `x`
  155. self.runCheck()
  156. def doCheck(self, x):
  157. #print 'doCheck:', self.page.status
  158. #if self.page.status != '200':
  159. # print 'foo'
  160. # return reactor.callLater(.01, self.runCheck)
  161. self.lastcheck = time.time()
  162. slm = self.page.info()['last-modified']
  163. if slm == self.lastmodified:
  164. # Page the same, don't do anything
  165. #self.pend = None
  166. return
  167. return self.parsePage(self.page)
  168. #self.page = getPage(self.url)
  169. #self.page.deferred.addCallback(self.parsePage)
  170. #self.pend = self.page.deferred
  171. #return self.pend
  172. def parsePage(self, page):
  173. slm = self.page.info()['last-modified']
  174. self.lastmodified = slm
  175. del self.page
  176. #self.pend = None
  177. self.newobjs = recxmltoobj(page.read())
  178. self.doUpdate()
  179. def genChildren(self):
  180. return self.newobjs.iterkeys()
  181. def createObject(self, i):
  182. return PYVRShows, i, (), { 'show': i, 'pyvr': self }
  183. def doUpdate(self):
  184. if self.newobjs is None:
  185. import traceback
  186. traceback.print_stack(file=log.logfile)
  187. raise ValueError('did not get shows')
  188. self.shows = self.newobjs
  189. Container.doUpdate(self)
  190. self.newobjs = None
  191. def detectpyvrfile(path, fobj):
  192. bn = os.path.basename(path)
  193. if bn == 'PYVR':
  194. return PYVR, { 'url': fobj.readline().strip(), 'path': path }
  195. return None, None
  196. registerklassfun(detectpyvrfile)