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.

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