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.

257 lines
6.0 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2008 John-Mark Gurney <jmg@funktaht.com>
  3. '''PVR Interface'''
  4. __version__ = '$Change: 1743 $'
  5. # $Id: //depot/python/pymeds/main/pyvr.py#10 $
  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 urllib.parse
  14. import urllib.request, urllib.error, urllib.parse
  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 = urllib.parse.urlparse(url)[0]
  41. if not sc:
  42. # need to combine w/ base url
  43. url = urllib.parse.urljoin(baseurl, url)
  44. self.res = Resource(url,
  45. 'http-get:*:%s:*' % self.info['mimetype'])
  46. self.res.duration = self.info['duration']
  47. try:
  48. self.date = self.info['startdateiso']
  49. except KeyError:
  50. pass
  51. def doUpdate(self):
  52. pass
  53. import xml.sax
  54. import xml.sax.handler
  55. from xml.sax.saxutils import unescape
  56. class RecordingXML(xml.sax.handler.ContentHandler):
  57. dataels = ('title', 'startdateiso', 'subtitle', 'duration',
  58. 'mimetype', 'link', 'delete', )
  59. def __init__(self):
  60. self.shows = {}
  61. self.data = None
  62. def characters(self, chars):
  63. if self.data is not None:
  64. self.data.append(chars)
  65. def startElement(self, name, attrs):
  66. if name in self.dataels:
  67. self.data = []
  68. self.curel = name
  69. elif name == 'record':
  70. self.currec = {}
  71. def endElement(self, name):
  72. if name in self.dataels:
  73. data = unescape(''.join(self.data))
  74. self.currec[self.curel] = data
  75. elif name == 'record':
  76. rec = self.currec
  77. try:
  78. self.shows[rec['title']].append(rec)
  79. except KeyError:
  80. self.shows[rec['title']] = [ rec ]
  81. self.data = None
  82. def recxmltoobj(page):
  83. obj = RecordingXML()
  84. xml.sax.parseString(page, obj)
  85. return obj.shows
  86. class PYVRShows(Container):
  87. def __init__(self, *args, **kwargs):
  88. self.pyvr = kwargs['pyvr']
  89. del kwargs['pyvr']
  90. self.show = kwargs['show']
  91. del kwargs['show']
  92. Container.__init__(self, *args, **kwargs)
  93. self.shows = {}
  94. self.lastmodified = None
  95. def checkUpdate(self):
  96. self.pyvr.checkUpdate()
  97. if self.pyvr.lastmodified != self.lastmodified:
  98. self.doUpdate()
  99. @staticmethod
  100. def getunique(eps, ep):
  101. i = 1
  102. while True:
  103. title = '%s Copy %d' % (ep['subtitle'], i)
  104. if title not in eps:
  105. return title
  106. i += 1
  107. @staticmethod
  108. def eplisttodict(eps):
  109. ret = {}
  110. for pos, i in enumerate(eps):
  111. title = i['subtitle']
  112. if title in ret:
  113. print('WARNING: dup:', repr(i), repr(ret[title]))
  114. title = PYVRShows.getunique(ret, i)
  115. i['pos'] = pos
  116. ret[title] = i
  117. return ret
  118. def genChildren(self):
  119. return self.eplisttodict(self.pyvr.shows[self.show])
  120. def createObject(self, i, arg):
  121. return PYVRShow, i, (), { 'url': self.pyvr.url, 'info': arg }
  122. def sort(self):
  123. Container.sort(self, lambda x, y: cmp(x.info['pos'],
  124. y.info['pos']))
  125. def doUpdate(self):
  126. Container.doUpdate(self)
  127. self.lastmodified = self.pyvr.lastmodified
  128. class PYVR(FSObject, Container):
  129. def __init__(self, *args, **kwargs):
  130. self.url = kwargs.pop('url')
  131. FSObject.__init__(self, kwargs.pop('path'))
  132. Container.__init__(self, *args, **kwargs)
  133. #self.pend = None
  134. self.lastmodified = None
  135. self.newobjs = None
  136. self.objs = {}
  137. self.lastcheck = 0
  138. def checkUpdate(self):
  139. #if self.pend is not None:
  140. # raise self.pend
  141. if time.time() - self.lastcheck < 10:
  142. return
  143. # Check to see if any changes have been made
  144. self.runCheck()
  145. def runCheck(self):
  146. while True:
  147. try:
  148. self.page = urllib.request.urlopen(self.url)
  149. break
  150. except urllib.error.HTTPError:
  151. time.sleep(.1)
  152. #self.page = getPage(self.url, method='HEAD')
  153. #self.page.deferred.addErrback(self.errCheck).addCallback(
  154. # self.doCheck)
  155. #self.pend = self.page.deferred
  156. return self.doCheck(self.page)
  157. def errCheck(self, x):
  158. print('errCheck:', repr(x))
  159. self.runCheck()
  160. def doCheck(self, x):
  161. #print 'doCheck:', self.page.status
  162. #if self.page.status != '200':
  163. # print 'foo'
  164. # return reactor.callLater(.01, self.runCheck)
  165. self.lastcheck = time.time()
  166. slm = self.page.info()['last-modified']
  167. if slm == self.lastmodified:
  168. # Page the same, don't do anything
  169. #self.pend = None
  170. return
  171. return self.parsePage(self.page)
  172. #self.page = getPage(self.url)
  173. #self.page.deferred.addCallback(self.parsePage)
  174. #self.pend = self.page.deferred
  175. #return self.pend
  176. def parsePage(self, page):
  177. slm = self.page.info()['last-modified']
  178. self.lastmodified = slm
  179. del self.page
  180. #self.pend = None
  181. self.newobjs = recxmltoobj(page.read())
  182. print('pp:', repr(self.newobjs))
  183. self.doUpdate()
  184. def genChildren(self):
  185. return list(self.newobjs.keys())
  186. def createObject(self, i, arg=None):
  187. print('co:', repr(i), repr(arg))
  188. return PYVRShows, i, (), { 'show': i, 'pyvr': self }
  189. def doUpdate(self):
  190. if self.newobjs is None:
  191. import traceback
  192. traceback.print_stack(file=log.logfile)
  193. raise ValueError('did not get shows')
  194. self.shows = self.newobjs
  195. Container.doUpdate(self)
  196. self.newobjs = None
  197. def detectpyvrfile(path, fobj):
  198. bn = os.path.basename(path)
  199. if bn == 'PYVR':
  200. return PYVR, { 'url': fobj.readline().strip(), 'path': path }
  201. return None, None
  202. registerklassfun(detectpyvrfile)