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.

225 lines
5.4 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2006 John-Mark Gurney <jmg@funkthat.com>
  3. '''DVD Handling'''
  4. __version__ = '$Change$'
  5. # $Id$
  6. default_audio_lang = 'en'
  7. import itertools
  8. import os
  9. import sets
  10. import sys
  11. sys.path.append('mpegts')
  12. try:
  13. import mpegts
  14. audiofilter = lambda x, y: mpegts.DVDAudioFilter(x, y)
  15. except ImportError:
  16. print >>sys.stderr, 'module mpegts could not be loaded, not filtering audio'
  17. audiofilter = lambda x, y: x
  18. from pydvdread import *
  19. from DIDLLite import StorageFolder, Movie, VideoItem, Resource
  20. from FSStorage import FSObject, registerklassfun
  21. from twisted.python import log, threadable
  22. from twisted.spread import pb
  23. from twisted.web import resource, server
  24. def gennameindexes(pref, item):
  25. ret = []
  26. d = {}
  27. for i, title in enumerate(item):
  28. t = '%s %d (%s)' % (pref, i + 1, title.time)
  29. ret.append(t)
  30. d[t] = i
  31. return ret, d
  32. class IterTransfer(pb.Viewable):
  33. def __init__(self, iterable, request):
  34. self.iter = iter(iterable)
  35. self.request = request
  36. request.registerProducer(self, 0)
  37. def resumeProducing(self):
  38. if not self.request:
  39. return
  40. # get data and write to request.
  41. try:
  42. data = self.iter.next()
  43. if data:
  44. # this .write will spin the reactor, calling
  45. # .doWrite and then .resumeProducing again, so
  46. # be prepared for a re-entrant call
  47. self.request.write(data)
  48. except StopIteration:
  49. if self.request:
  50. self.request.unregisterProducer()
  51. self.request.finish()
  52. self.request = None
  53. def pauseProducing(self):
  54. pass
  55. def stopProducing(self):
  56. # close zipfile
  57. self.request = None
  58. # Remotely relay producer interface.
  59. def view_resumeProducing(self, issuer):
  60. self.resumeProducing()
  61. def view_pauseProducing(self, issuer):
  62. self.pauseProducing()
  63. def view_stopProducing(self, issuer):
  64. self.stopProducing()
  65. synchronized = ['resumeProducing', 'stopProducing']
  66. threadable.synchronize(IterTransfer)
  67. class IterGenResource(resource.Resource):
  68. isLeaf = True
  69. def __init__(self, itergen):
  70. resource.Resource.__init__(self)
  71. self.itergen = itergen
  72. def render(self, request):
  73. request.setHeader('content-type', 'video/mpeg')
  74. if request.method == 'HEAD':
  75. return ''
  76. # return data
  77. IterTransfer(self.itergen(), request)
  78. # and make sure the connection doesn't get closed
  79. return server.NOT_DONE_YET
  80. class DVDChapter(VideoItem):
  81. def __init__(self, *args, **kwargs):
  82. self.dvdtitle = kwargs['dvdtitle']
  83. self.chapter = kwargs['chapter']
  84. del kwargs['dvdtitle'], kwargs['chapter']
  85. audio = self.dvdtitle.selectaudio(default_audio_lang)
  86. kwargs['content'] = IterGenResource(lambda i = self.chapter,
  87. p = audio.pos: audiofilter(i, 0x80 + p))
  88. VideoItem.__init__(self, *args, **kwargs)
  89. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  90. self.res = Resource(self.url, 'http-get:*:video/mpeg:*')
  91. #self.res.size = self.chapter.size
  92. def doUpdate(self):
  93. pass
  94. class DVDTitle(StorageFolder):
  95. def __init__(self, *args, **kwargs):
  96. self.dvdtitle = kwargs['dvdtitle']
  97. self.dvddisc = kwargs['dvddisc']
  98. del kwargs['dvdtitle'], kwargs['dvddisc']
  99. audio = self.dvdtitle.selectaudio(default_audio_lang)
  100. kwargs['content'] = IterGenResource(lambda dt = self.dvdtitle,
  101. p = audio.pos: audiofilter(itertools.chain(*dt), 0x80 + p))
  102. StorageFolder.__init__(self, *args, **kwargs)
  103. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  104. self.res = Resource(self.url, 'http-get:*:video/mpeg:*')
  105. # mapping from path to objectID
  106. self.pathObjmap = {}
  107. def checkUpdate(self):
  108. self.doUpdate()
  109. #return self.dvddisc.checkUpdate()
  110. def doUpdate(self):
  111. doupdate = False
  112. origchildren, toindex = gennameindexes('Chapter', self.dvdtitle)
  113. children = sets.Set(origchildren)
  114. for i in self.pathObjmap.keys():
  115. if i not in children:
  116. doupdate = True
  117. # delete
  118. self.cd.delItem(self.pathObjmap[i])
  119. del self.pathObjmap[i]
  120. for i in origchildren:
  121. if i in self.pathObjmap:
  122. continue
  123. # new object
  124. self.pathObjmap[i] = self.cd.addItem(self.id,
  125. DVDChapter, i, dvdtitle = self.dvdtitle,
  126. chapter = self.dvdtitle[toindex[i]])
  127. doupdate = True
  128. if doupdate:
  129. StorageFolder.doUpdate(self)
  130. class DVDDisc(FSObject, StorageFolder):
  131. def __init__(self, *args, **kwargs):
  132. path = kwargs['path']
  133. del kwargs['path']
  134. StorageFolder.__init__(self, *args, **kwargs)
  135. FSObject.__init__(self, path)
  136. # mapping from path to objectID
  137. self.pathObjmap = {}
  138. def doUpdate(self):
  139. # open the DVD as necessary.
  140. self.dvd = DVD(self.FSpath)
  141. doupdate = False
  142. origchildren, toindex = gennameindexes('Title', self.dvd)
  143. children = sets.Set(origchildren)
  144. for i in self.pathObjmap.keys():
  145. if i not in children:
  146. doupdate = True
  147. # delete
  148. self.cd.delItem(self.pathObjmap[i])
  149. del self.pathObjmap[i]
  150. for i in origchildren:
  151. if i in self.pathObjmap:
  152. continue
  153. # new object
  154. self.pathObjmap[i] = self.cd.addItem(self.id, DVDTitle,
  155. i, dvdtitle = self.dvd[toindex[i]], dvddisc = self)
  156. doupdate = True
  157. if doupdate:
  158. StorageFolder.doUpdate(self)
  159. def detectdvd(path, fobj):
  160. if os.path.isdir(path):
  161. # Make sure we there is only a VIDEO_TS in there, even
  162. # if there is a VIDEO_TS w/ other files, we will open
  163. # the VIDEO_TS as a DVD (if it is one)
  164. ld = os.listdir(path)
  165. if ld == ['VIDEO_TS' ]:
  166. pass
  167. elif not filter(lambda x: x[:4] != 'VTS_' and
  168. x[:9] != 'VIDEO_TS.', ld):
  169. pass
  170. else:
  171. return None, None
  172. d = DVD(path)
  173. return DVDDisc, { 'path': path }
  174. registerklassfun(detectdvd)