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.

288 lines
6.5 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
  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.pathObjmap = {}
  90. self.shows = {}
  91. self.lastmodified = None
  92. def checkUpdate(self):
  93. self.pyvr.checkUpdate()
  94. if self.pyvr.lastmodified != self.lastmodified:
  95. self.doUpdate()
  96. @staticmethod
  97. def getunique(eps, ep):
  98. i = 1
  99. while True:
  100. title = '%s Copy %d' % (ep['subtitle'], i)
  101. if not eps.has_key(title):
  102. return title
  103. i += 1
  104. @staticmethod
  105. def eplisttodict(eps):
  106. ret = {}
  107. for pos, i in enumerate(eps):
  108. title = i['subtitle']
  109. if ret.has_key(title):
  110. print 'WARNING: dup:', `i`, `ret[title]`
  111. title = PYVRShows.getunique(ret, i)
  112. i['pos'] = pos
  113. ret[title] = i
  114. return ret
  115. def doUpdate(self):
  116. nl = self.eplisttodict(self.pyvr.shows[self.show])
  117. doupdate = False
  118. for i in self.pathObjmap.keys():
  119. if i not in nl:
  120. # delete
  121. doupdate = True
  122. self.cd.delItem(self.pathObjmap[i])
  123. del self.pathObjmap[i]
  124. for i in nl:
  125. if i in self.pathObjmap and self.shows[i] == nl[i]:
  126. continue
  127. doupdate = True
  128. if i in self.pathObjmap:
  129. # changed
  130. self.cd.delItem(self.pathObjmap[i])
  131. self.pathObjmap[i] = self.cd.addItem(self.id,
  132. PYVRShow, i, url=self.pyvr.url, info=nl[i])
  133. self.shows = nl
  134. # sort our children
  135. #self.sort(lambda x, y: cmp(x.title, y.title))
  136. self.sort(lambda x, y: cmp(x.info['pos'], y.info['pos']))
  137. if doupdate:
  138. Container.doUpdate(self)
  139. self.lastmodified = self.pyvr.lastmodified
  140. class PYVR(Container):
  141. def __init__(self, *args, **kwargs):
  142. self.url = kwargs['url']
  143. del kwargs['url']
  144. Container.__init__(self, *args, **kwargs)
  145. self.pathObjmap = {}
  146. #self.pend = None
  147. self.lastmodified = None
  148. self.newobjs = None
  149. self.objs = {}
  150. self.lastcheck = 0
  151. def checkUpdate(self):
  152. #if self.pend is not None:
  153. # raise self.pend
  154. if time.time() - self.lastcheck < 10:
  155. return
  156. # Check to see if any changes have been made
  157. self.runCheck()
  158. def runCheck(self):
  159. while True:
  160. try:
  161. self.page = urllib2.urlopen(self.url)
  162. break
  163. except urllib2.HTTPError:
  164. time.sleep(.1)
  165. #self.page = getPage(self.url, method='HEAD')
  166. #self.page.deferred.addErrback(self.errCheck).addCallback(
  167. # self.doCheck)
  168. #self.pend = self.page.deferred
  169. return self.doCheck(self.page)
  170. def errCheck(self, x):
  171. print 'errCheck:', `x`
  172. self.runCheck()
  173. def doCheck(self, x):
  174. #print 'doCheck:', self.page.status
  175. #if self.page.status != '200':
  176. # print 'foo'
  177. # return reactor.callLater(.01, self.runCheck)
  178. self.lastcheck = time.time()
  179. slm = self.page.info()['last-modified']
  180. if slm == self.lastmodified:
  181. # Page the same, don't do anything
  182. #self.pend = None
  183. return
  184. return self.parsePage(self.page)
  185. #self.page = getPage(self.url)
  186. #self.page.deferred.addCallback(self.parsePage)
  187. #self.pend = self.page.deferred
  188. #return self.pend
  189. def parsePage(self, page):
  190. slm = self.page.info()['last-modified']
  191. self.lastmodified = slm
  192. del self.page
  193. #self.pend = None
  194. self.newobjs = recxmltoobj(page.read())
  195. self.doUpdate()
  196. def doUpdate(self):
  197. if self.newobjs is None:
  198. import traceback
  199. traceback.print_stack(file=log.logfile)
  200. return
  201. nl = self.newobjs
  202. doupdate = False
  203. for i in self.pathObjmap.keys():
  204. if i not in nl:
  205. # delete
  206. doupdate = True
  207. self.cd.delItem(self.pathObjmap[i])
  208. del self.pathObjmap[i]
  209. # This data is referenced when adding new shows
  210. self.shows = nl
  211. for i in nl:
  212. if i in self.pathObjmap:
  213. continue
  214. doupdate = True
  215. try:
  216. self.pathObjmap[i] = self.cd.addItem(self.id,
  217. PYVRShows, i, show=i, pyvr=self)
  218. except:
  219. import traceback
  220. traceback.print_exc(file=log.logfile)
  221. raise
  222. self.newobjs = None
  223. # sort our children
  224. self.sort(lambda x, y: cmp(x.title, y.title))
  225. if doupdate:
  226. Container.doUpdate(self)
  227. def detectpyvrfile(path, fobj):
  228. bn = os.path.basename(path)
  229. if bn == 'PYVR':
  230. return PYVR, { 'url': fobj.readline().strip() }
  231. return None, None
  232. registerklassfun(detectpyvrfile)