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.

174 lines
4.2 KiB

  1. #!/usr/bin/env python
  2. # Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
  3. '''Audio Raw Converter'''
  4. from DIDLLite import AudioItem, Resource, ResourceList
  5. from FSStorage import registerklassfun
  6. from twisted.web import resource, server
  7. from twisted.internet.interfaces import IPullProducer
  8. from zope.interface import implements
  9. decoders = {}
  10. try:
  11. import flac
  12. decoders['flac'] = flac.FLACDec
  13. except ImportError:
  14. pass
  15. mtformat = {
  16. 16: 'audio/l16', # BE signed
  17. # 8: 'audio/l8', unsigned
  18. }
  19. def makemtfromdec(dec):
  20. return '%s;rate=%d;channels=%d' % (mtformat[dec.bitspersample],
  21. dec.samplerate, dec.channels)
  22. class DecoderProducer:
  23. implements(IPullProducer)
  24. def __init__(self, consumer, decoder, tbytes, skipbytes):
  25. self.decoder = decoder
  26. self.consumer = consumer
  27. self.tbytes = tbytes
  28. self.skipbytes = skipbytes
  29. consumer.registerProducer(self, False)
  30. def resumeProducing(self):
  31. r = self.decoder.read(8*1024)
  32. if r:
  33. #print 'DPrP:', len(r)
  34. if self.skipbytes:
  35. cnt = min(self.skipbytes, len(r))
  36. r = r[cnt:]
  37. self.skipbytes -= cnt
  38. send = min(len(r), self.tbytes)
  39. r = r[:send]
  40. self.tbytes -= len(r)
  41. self.consumer.write(r)
  42. if self.tbytes:
  43. return
  44. print 'DPurP'
  45. self.consumer.unregisterProducer()
  46. def stopProducing(self):
  47. print 'DPsP'
  48. self.decoder.close()
  49. self.decoder = None
  50. class AudioResource(resource.Resource):
  51. isLeaf = True
  52. def __init__(self, f, dec):
  53. resource.Resource.__init__(self)
  54. self.f = f
  55. self.dec = dec
  56. def calcrange(self, rng, l):
  57. rng = rng.strip()
  58. unit, rangeset = rng.split('=')
  59. assert unit == 'bytes', `unit`
  60. start, end = rangeset.split('-')
  61. start = int(start)
  62. if end:
  63. end = int(end)
  64. else:
  65. end = l
  66. return start, end - start + 1
  67. def render(self, request):
  68. decoder = self.dec(self.f)
  69. request.setHeader('content-type', makemtfromdec(decoder))
  70. bytespersample = decoder.channels * decoder.bitspersample / 8
  71. tbytes = decoder.totalsamples * bytespersample
  72. skipbytes = 0
  73. request.setHeader('content-length', tbytes)
  74. request.setHeader('accept-ranges', 'bytes')
  75. if request.requestHeaders.hasHeader('range'):
  76. print 'range req:', `request.requestHeaders.getRawHeaders('range')`
  77. start, cnt = self.calcrange(
  78. request.requestHeaders.getRawHeaders('range')[0],
  79. tbytes)
  80. skipbytes = start % bytespersample
  81. print 'going:', start / bytespersample
  82. decoder.goto(start / bytespersample)
  83. print 'there'
  84. request.setHeader('content-length', cnt)
  85. request.setHeader('content-range', 'bytes %s-%s/%s' %
  86. (start, start + cnt - 1, tbytes))
  87. tbytes = cnt
  88. if request.method == 'HEAD':
  89. return ''
  90. DecoderProducer(request, decoder, tbytes, skipbytes)
  91. print 'producing render'
  92. # and make sure the connection doesn't get closed
  93. return server.NOT_DONE_YET
  94. class AudioRaw(AudioItem):
  95. def __init__(self, *args, **kwargs):
  96. self.file = kwargs.pop('file')
  97. nchan = kwargs.pop('channels')
  98. samprate = kwargs.pop('samplerate')
  99. bitsps = kwargs.pop('bitspersample')
  100. samples = kwargs.pop('samples')
  101. self.totalbytes = nchan * samples * bitsps / 8
  102. try:
  103. mt = mtformat[bitsps]
  104. except KeyError:
  105. raise KeyError('No mime-type for audio format: %s.' %
  106. `origfmt`)
  107. self.mt = '%s;rate=%d;channels=%d' % (mt, samprate, nchan)
  108. kwargs['content'] = AudioResource(self.file,
  109. kwargs.pop('decoder'))
  110. AudioItem.__init__(self, *args, **kwargs)
  111. self.url = '%s/%s' % (self.cd.urlbase, self.id)
  112. self.res = ResourceList()
  113. r = Resource(self.url, 'http-get:*:%s:*' % self.mt)
  114. r.size = self.totalbytes
  115. r.duration = float(samples) / samprate
  116. r.bitrate = nchan * samprate * bitsps / 8
  117. r.sampleFrequency = samprate
  118. r.bitsPerSample = bitsps
  119. r.nrAudioChannels = nchan
  120. self.res.append(r)
  121. def detectaudioraw(origpath, fobj):
  122. for i in decoders.itervalues():
  123. try:
  124. obj = i(origpath)
  125. # XXX - don't support down sampling yet
  126. if obj.bitspersample not in (8, 16):
  127. continue
  128. return AudioRaw, {
  129. 'decoder': i,
  130. 'file': origpath,
  131. 'channels': obj.channels,
  132. 'samplerate': obj.samplerate,
  133. 'bitspersample': obj.bitspersample,
  134. 'samples': obj.totalsamples,
  135. }
  136. except:
  137. #import traceback
  138. #traceback.print_exc()
  139. pass
  140. return None, None
  141. registerklassfun(detectaudioraw, True)