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.7 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2006 John-Mark Gurney <gurney_j@resnet.uoregon.edu>
  3. '''MPEG-TS Handling'''
  4. __version__ = '$Change$'
  5. # $Id$
  6. tsselpypath = '/Users/jgurney/p4/bktrau/info/tssel.py'
  7. default_audio_lang = 'eng'
  8. import os
  9. import sets
  10. import sys
  11. mpegtspath = '/Users/jgurney/p4/bktrau/info'
  12. if mpegtspath not in sys.path:
  13. sys.path.append(mpegtspath)
  14. import mpegts
  15. import tssel
  16. from DIDLLite import StorageFolder, VideoItem, Resource
  17. from FSStorage import FSObject, registerklassfun
  18. from twisted.python import log, threadable
  19. from twisted.spread import pb
  20. from twisted.internet import process, protocol, reactor
  21. from twisted.web import resource, server
  22. class _LimitedFile(file):
  23. def __init__(self, *args, **kwargs):
  24. self.__size = kwargs['size']
  25. del kwargs['size']
  26. file.__init__(self, *args, **kwargs)
  27. def remain(self):
  28. pos = self.tell()
  29. if pos > self.__size:
  30. return 0
  31. return self.__size - pos
  32. def read(self, size=-1):
  33. if size < 0:
  34. return file.read(self, self.remain())
  35. return file.read(self, min(size, self.remain()))
  36. def _gennameindexes(chan):
  37. ret = []
  38. d = {}
  39. for i in chan:
  40. t = '%s %s.%s' % (i['name'], i['major'], i['minor'])
  41. ret.append(t)
  42. d[t] = i
  43. return ret, d
  44. class MPEGTSTransfer(pb.Viewable):
  45. def __init__(self, iterable, request):
  46. self.iter = iter(iterable)
  47. self.request = request
  48. request.registerProducer(self, 0)
  49. def resumeProducing(self):
  50. if not self.request:
  51. return
  52. # get data and write to request.
  53. try:
  54. data = self.iter.next()
  55. if data:
  56. # this .write will spin the reactor, calling
  57. # .doWrite and then .resumeProducing again, so
  58. # be prepared for a re-entrant call
  59. self.request.write(data)
  60. except StopIteration:
  61. if self.request:
  62. self.request.unregisterProducer()
  63. self.request.finish()
  64. self.request = None
  65. def pauseProducing(self):
  66. pass
  67. def stopProducing(self):
  68. # close zipfile
  69. self.request = None
  70. # Remotely relay producer interface.
  71. def view_resumeProducing(self, issuer):
  72. self.resumeProducing()
  73. def view_pauseProducing(self, issuer):
  74. self.pauseProducing()
  75. def view_stopProducing(self, issuer):
  76. self.stopProducing()
  77. synchronized = ['resumeProducing', 'stopProducing']
  78. threadable.synchronize(MPEGTSTransfer)
  79. class DynamTSTransfer(protocol.ProcessProtocol):
  80. def __init__(self, path, pmt, *pids):
  81. self.path = path
  82. self.pmt = pmt
  83. self.pids = pids
  84. def outReceived(self, data):
  85. self.request.write(data)
  86. def outConnectionLost(self):
  87. if self.request:
  88. self.request.unregisterProducer()
  89. self.request.finish()
  90. self.request = None
  91. def errReceived(self, data):
  92. pass
  93. #log.msg(data)
  94. def stopProducing(self):
  95. if self.request:
  96. self.request.unregisterProducer()
  97. self.request.finish()
  98. if self.proc:
  99. self.proc.loseConnection()
  100. self.proc.signalProcess('INT')
  101. self.request = None
  102. self.proc = None
  103. pauseProducing = lambda x: x.proc.pauseProducing()
  104. resumeProducing = lambda x: x.proc.resumeProducing()
  105. def render(self, request):
  106. path = self.path
  107. pmt = self.pmt
  108. pids = self.pids
  109. self.request = request
  110. request.setHeader('content-type', 'video/mpeg')
  111. if request.method == 'HEAD':
  112. return ''
  113. args = [ 'tssel.py', path, str(pmt), ] + map(str, pids)
  114. self.proc = process.Process(reactor, tsselpypath, args,
  115. None, None, self)
  116. self.proc.closeStdin()
  117. request.registerProducer(self, 1)
  118. return server.NOT_DONE_YET
  119. class MPEGTSResource(resource.Resource):
  120. isLeaf = True
  121. def __init__(self, *args):
  122. resource.Resource.__init__(self)
  123. self.args = args
  124. def render(self, request):
  125. request.setHeader('content-type', 'video/mpeg')
  126. if request.method == 'HEAD':
  127. return ''
  128. # return data
  129. return DynamTSTransfer(*self.args).render(request)
  130. class MPEGTS(FSObject, VideoItem):
  131. def __init__(self, *args, **kwargs):
  132. self.path = path = kwargs['path']
  133. del kwargs['path']
  134. self.tvct = tvct = kwargs['tvct']
  135. del kwargs['tvct']
  136. #log.msg('tvct w/ keys:', tvct, tvct.keys())
  137. kwargs['content'] = MPEGTSResource(path, tvct['PMTpid'],
  138. *sum(mpegts.getaudiovideopids(tvct['PMT']), []))
  139. VideoItem.__init__(self, *args, **kwargs)
  140. FSObject.__init__(self, path)
  141. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  142. self.res = Resource(self.url, 'http-get:*:video/mpeg:*')
  143. def doUpdate(self):
  144. pass
  145. class MultiMPEGTS(FSObject, StorageFolder):
  146. def __init__(self, *args, **kwargs):
  147. path = kwargs['path']
  148. del kwargs['path']
  149. StorageFolder.__init__(self, *args, **kwargs)
  150. FSObject.__init__(self, path)
  151. # mapping from path to objectID
  152. self.pathObjmap = {}
  153. def doUpdate(self):
  154. f = mpegts.TSPStream(_LimitedFile(self.FSpath,
  155. size= 2*1024*1024))
  156. self.tvct = mpegts.GetTVCT(f)
  157. doupdate = False
  158. origchildren, toindex = _gennameindexes(self.tvct['channels'])
  159. children = sets.Set(origchildren)
  160. for i in self.pathObjmap.keys():
  161. if i not in children:
  162. doupdate = True
  163. # delete
  164. self.cd.delItem(self.pathObjmap[i])
  165. del self.pathObjmap[i]
  166. for i in origchildren:
  167. if i in self.pathObjmap:
  168. continue
  169. # new object
  170. if toindex[i]['prog_num'] == 0:
  171. log.msg('bogus:', toindex[i])
  172. continue
  173. #log.msg('real tvct:', toindex[i], toindex.keys(),
  174. # self.tvct)
  175. self.pathObjmap[i] = self.cd.addItem(self.id, MPEGTS,
  176. i, path = self.FSpath, tvct = toindex[i])
  177. doupdate = True
  178. if doupdate:
  179. StorageFolder.doUpdate(self)
  180. def detectmpegts(path, fobj):
  181. f = mpegts.TSPStream(_LimitedFile(path, size= 2*1024*1024))
  182. tvct = mpegts.GetTVCT(f)
  183. if len(tvct['channels']) == 1:
  184. #return None, None
  185. # We might reenable this once we have pid filtering working
  186. # fast enough.
  187. return MPEGTS, { 'path': path, 'tvct': tvct['channels'][0] }
  188. elif len(tvct['channels']) > 1:
  189. return MultiMPEGTS, { 'path': path }
  190. return None, None
  191. registerklassfun(detectmpegts)