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.

283 lines
6.4 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. def getPage(url, contextFactory=None, *args, **kwargs):
  15. """Download a web page as a string.
  16. Download a page. Return the HTTPClientFactory, which will
  17. callback with a page (as a string) or errback with a
  18. description of the error.
  19. See HTTPClientFactory to see what extra args can be passed.
  20. """
  21. from twisted.web.client import _parse, HTTPClientFactory
  22. scheme, host, port, path = _parse(url)
  23. factory = HTTPClientFactory(url, *args, **kwargs)
  24. if scheme == 'https':
  25. from twisted.internet import ssl
  26. if contextFactory is None:
  27. contextFactory = ssl.ClientContextFactory()
  28. reactor.connectSSL(host, port, factory, contextFactory)
  29. else:
  30. reactor.connectTCP(host, port, factory)
  31. return factory
  32. class PYVRShow(VideoItem):
  33. def __init__(self, *args, **kwargs):
  34. baseurl = kwargs['url']
  35. self.info = kwargs['info']
  36. del kwargs['info'], kwargs['url']
  37. VideoItem.__init__(self, *args, **kwargs)
  38. url = self.info['link']
  39. sc = urlparse.urlparse(url)[0]
  40. if not sc:
  41. # need to combine w/ base url
  42. url = urlparse.urljoin(baseurl, url)
  43. self.res = Resource(url,
  44. 'http-get:*:%s:*' % self.info['mimetype'])
  45. self.res.duration = self.info['duration']
  46. def doUpdate(self):
  47. pass
  48. import xml.sax
  49. import xml.sax.handler
  50. from xml.sax.saxutils import unescape
  51. class RecordingXML(xml.sax.handler.ContentHandler):
  52. dataels = ('title', 'subtitle', 'duration', 'mimetype', 'link',
  53. 'delete', )
  54. def __init__(self):
  55. self.shows = {}
  56. self.data = None
  57. def characters(self, chars):
  58. if self.data is not None:
  59. self.data.append(chars)
  60. def startElement(self, name, attrs):
  61. if name in self.dataels:
  62. self.data = []
  63. self.curel = name
  64. elif name == 'record':
  65. self.currec = {}
  66. def endElement(self, name):
  67. if name in self.dataels:
  68. data = unescape(''.join(self.data))
  69. self.currec[self.curel] = data
  70. elif name == 'record':
  71. rec = self.currec
  72. try:
  73. self.shows[rec['title']].append(rec)
  74. except KeyError:
  75. self.shows[rec['title']] = [ rec ]
  76. self.data = None
  77. def recxmltoobj(page):
  78. obj = RecordingXML()
  79. xml.sax.parseString(page, obj)
  80. return obj.shows
  81. class PYVRShows(Container):
  82. def __init__(self, *args, **kwargs):
  83. self.pyvr = kwargs['pyvr']
  84. del kwargs['pyvr']
  85. self.show = kwargs['show']
  86. del kwargs['show']
  87. Container.__init__(self, *args, **kwargs)
  88. self.pathObjmap = {}
  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 doUpdate(self):
  115. nl = self.eplisttodict(self.pyvr.shows[self.show])
  116. doupdate = False
  117. for i in self.pathObjmap.keys():
  118. if i not in nl:
  119. # delete
  120. doupdate = True
  121. self.cd.delItem(self.pathObjmap[i])
  122. del self.pathObjmap[i]
  123. for i in nl:
  124. if i in self.pathObjmap and self.shows[i] == nl[i]:
  125. continue
  126. doupdate = True
  127. if i in self.pathObjmap:
  128. # changed
  129. self.cd.delItem(self.pathObjmap[i])
  130. self.pathObjmap[i] = self.cd.addItem(self.id,
  131. PYVRShow, i, url=self.pyvr.url, info=nl[i])
  132. self.shows = nl
  133. # sort our children
  134. #self.sort(lambda x, y: cmp(x.title, y.title))
  135. self.sort(lambda x, y: cmp(x.info['pos'], y.info['pos']))
  136. if doupdate:
  137. Container.doUpdate(self)
  138. self.lastmodified = self.pyvr.lastmodified
  139. class PYVR(Container):
  140. def __init__(self, *args, **kwargs):
  141. self.url = kwargs['url']
  142. del kwargs['url']
  143. Container.__init__(self, *args, **kwargs)
  144. self.pathObjmap = {}
  145. self.pend = None
  146. self.lastmodified = None
  147. self.newobjs = None
  148. self.objs = {}
  149. self.lastcheck = 0
  150. def checkUpdate(self):
  151. if self.pend is not None:
  152. raise self.pend
  153. if time.time() - self.lastcheck < 5:
  154. print '<5'
  155. return
  156. # Check to see if any changes have been made
  157. self.runCheck()
  158. raise self.pend
  159. def runCheck(self):
  160. print 'runCheck'
  161. self.page = getPage(self.url, method='HEAD')
  162. self.page.deferred.addErrback(self.errCheck).addCallback(
  163. self.doCheck)
  164. self.pend = self.page.deferred
  165. def errCheck(self, x):
  166. print 'errCheck:', `x`
  167. self.runCheck()
  168. def doCheck(self, x):
  169. print 'doCheck:', self.page.status
  170. if self.page.status != '200':
  171. print 'foo'
  172. return reactor.callLater(.01, self.runCheck)
  173. self.lastcheck = time.time()
  174. slm = self.page.response_headers['last-modified']
  175. if slm == self.lastmodified:
  176. # Page the same, don't do anything
  177. self.pend = None
  178. return
  179. self.page = getPage(self.url)
  180. self.page.deferred.addCallback(self.parsePage)
  181. self.pend = self.page.deferred
  182. return self.pend
  183. def parsePage(self, page):
  184. slm = self.page.response_headers['last-modified']
  185. self.lastmodified = slm
  186. del self.page
  187. self.pend = None
  188. self.newobjs = recxmltoobj(page)
  189. self.doUpdate()
  190. def doUpdate(self):
  191. if self.newobjs is None:
  192. import traceback
  193. traceback.print_stack(file=log.logfile)
  194. return
  195. nl = self.newobjs
  196. doupdate = False
  197. for i in self.pathObjmap.keys():
  198. if i not in nl:
  199. # delete
  200. doupdate = True
  201. self.cd.delItem(self.pathObjmap[i])
  202. del self.pathObjmap[i]
  203. # This data is referenced when adding new shows
  204. self.shows = nl
  205. for i in nl:
  206. if i in self.pathObjmap:
  207. continue
  208. doupdate = True
  209. try:
  210. self.pathObjmap[i] = self.cd.addItem(self.id,
  211. PYVRShows, i, show=i, pyvr=self)
  212. except:
  213. import traceback
  214. traceback.print_exc(file=log.logfile)
  215. raise
  216. self.newobjs = None
  217. # sort our children
  218. self.sort(lambda x, y: cmp(x.title, y.title))
  219. if doupdate:
  220. Container.doUpdate(self)
  221. def detectpyvrfile(path, fobj):
  222. bn = os.path.basename(path)
  223. if bn == 'PYVR':
  224. return PYVR, { 'url': fobj.readline().strip() }
  225. return None, None
  226. registerklassfun(detectpyvrfile)