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.

282 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 < 10:
  154. return
  155. # Check to see if any changes have been made
  156. self.runCheck()
  157. raise self.pend
  158. def runCheck(self):
  159. print 'runCheck'
  160. self.page = getPage(self.url, method='HEAD')
  161. self.page.deferred.addErrback(self.errCheck).addCallback(
  162. self.doCheck)
  163. self.pend = self.page.deferred
  164. def errCheck(self, x):
  165. print 'errCheck:', `x`
  166. self.runCheck()
  167. def doCheck(self, x):
  168. print 'doCheck:', self.page.status
  169. if self.page.status != '200':
  170. print 'foo'
  171. return reactor.callLater(.01, self.runCheck)
  172. self.lastcheck = time.time()
  173. slm = self.page.response_headers['last-modified']
  174. if slm == self.lastmodified:
  175. # Page the same, don't do anything
  176. self.pend = None
  177. return
  178. self.page = getPage(self.url)
  179. self.page.deferred.addCallback(self.parsePage)
  180. self.pend = self.page.deferred
  181. return self.pend
  182. def parsePage(self, page):
  183. slm = self.page.response_headers['last-modified']
  184. self.lastmodified = slm
  185. del self.page
  186. self.pend = None
  187. self.newobjs = recxmltoobj(page)
  188. self.doUpdate()
  189. def doUpdate(self):
  190. if self.newobjs is None:
  191. import traceback
  192. traceback.print_stack(file=log.logfile)
  193. return
  194. nl = self.newobjs
  195. doupdate = False
  196. for i in self.pathObjmap.keys():
  197. if i not in nl:
  198. # delete
  199. doupdate = True
  200. self.cd.delItem(self.pathObjmap[i])
  201. del self.pathObjmap[i]
  202. # This data is referenced when adding new shows
  203. self.shows = nl
  204. for i in nl:
  205. if i in self.pathObjmap:
  206. continue
  207. doupdate = True
  208. try:
  209. self.pathObjmap[i] = self.cd.addItem(self.id,
  210. PYVRShows, i, show=i, pyvr=self)
  211. except:
  212. import traceback
  213. traceback.print_exc(file=log.logfile)
  214. raise
  215. self.newobjs = None
  216. # sort our children
  217. self.sort(lambda x, y: cmp(x.title, y.title))
  218. if doupdate:
  219. Container.doUpdate(self)
  220. def detectpyvrfile(path, fobj):
  221. bn = os.path.basename(path)
  222. if bn == 'PYVR':
  223. return PYVR, { 'url': fobj.readline().strip() }
  224. return None, None
  225. registerklassfun(detectpyvrfile)