#!/usr/bin/env python
# Copyright 2009 John-Mark Gurney <jmg@funkthat.com>
'''Audio Source'''

__version__ = '$Change: 1366 $'
# $Id: //depot/python/pymeds/main/shoutcast.py#24 $

import ossaudiodev
import os.path

from DIDLLite import Container, MusicGenre, AudioItem, Resource, ResourceList
from FSStorage import registerklassfun

from twisted.internet.abstract import FileDescriptor
from twisted.internet import fdesc
from twisted.python import log, threadable, failure
from twisted.web import error, http, resource, server

from zope.interface import implements

mttobytes = {
	'audio/l8':	1,
	'audio/l16':	2,
}

def bytespersecmt(mt):
	tmp = [ x.strip() for x in mt.split(';') ]
	try:
		r = mttobytes[tmp[0].lower()]
	except KeyError:
		raise ValueError('invalid audio type: %s' % `tmp[0]`)

	v = set(('rate', 'channels'))
	for i in tmp[1:]:
		arg, value = [ x.strip() for x in i.split('=', 1) ]
		if arg in v:
			v.remove(arg)
			r *= int(value)
		else:
			raise ValueError('invalid audio parameter %s in %s' %
			    (`arg`, `mt`))

	return r

class AudioPlayer(FileDescriptor):
	def __init__(self, consumer, dev, mode, params):
		self._dev = ossaudiodev.open(dev, mode)

		# Set some sub-functions
		self.fileno = self._dev.fileno
		self.setparameters = self._dev.setparameters

		res = self.setparameters(*params)
		self._dev.nonblock()

		FileDescriptor.__init__(self)

		self.connected = True
		self.attached = consumer
		self.writefun = self.attached.write
		consumer.registerProducer(self, True)
		self.dobuffer = False
		self.buffer = None
		self.startReading()

		# Drop our useless write connection
		self._writeDisconnected = True

	def writeSomeData(self, data):
		print 'wsd:', len(data)
		return fdesc.writeToFD(self.fileno(), data)

	def doRead(self):
		return fdesc.readFromFD(self.fileno(), self.writefun)

	def connectionLost(self, reason):
		FileDescriptor.connectionLost(self, reason)

		print 'AP, connectionLost'
		self.fileno = lambda: -1
		self.setparameters = None
		if self._dev is not None:
			self._dev.close()
		self._dev = None
		self.attached = None

	def stopProducing(self):
		print 'AP, sp'
		self.writefun = lambda x: None
		FileDescriptor.stopProducing(self)

	def pauseProducing(self):
		if not self.dobuffer:
			self.buffer = []
			self.dobuffer = True
			self.writefun = self.buffer.append
		#FileDescriptor.pauseProducing(self)

	def resumeProducing(self):
		if self.dobuffer:
			self.attached.write(''.join(self.buffer))
		self.dobuffer = False
		self.buffer = None
		self.writefun = self.attached.write
		#FileDescriptor.resumeProducing(self)

	def __repr__(self):
		return '<AudioPlayer: fileno: %d, connected: %s, self.disconnecting: %s, _writeDisconnected: %s>' % (self.fileno(), self.connected, self.disconnecting, self._writeDisconnected)
class AudioResource(resource.Resource):
	isLeaf = True
	mtformat = {
		ossaudiodev.AFMT_S16_BE: 'audio/L16',
		ossaudiodev.AFMT_U8: 'audio/L8',
	}
	producerFactory = AudioPlayer

	def __init__(self, dev, default):
		resource.Resource.__init__(self)
		self.dev = dev
		self.default = default

	@staticmethod
	def getfmt(fmt):
		return getattr(ossaudiodev, 'AFMT_%s' % fmt)

	def getmimetype(self, *args):
		if len(args) == 0:
			args = self.default
		elif len(args) != 3:
			raise TypeError('getmimetype() takes exactly 0 or 3 aruments (%d given)' % len(args))

		fmt, nchan, rate = args
		origfmt = fmt
		try:
			fmt = getattr(ossaudiodev, 'AFMT_%s' % fmt)
			nchan = int(nchan)
			rate = int(rate)
		except AttributeError:
			raise ValueError('Invalid audio format: %s' % `origfmt`)

		try:
			mt = self.mtformat[fmt]
		except KeyError:
			raise KeyError('No mime-type for audio format: %s.' %
			    `origfmt`)

		return '%s;rate=%d;channels=%d' % (mt, rate, nchan)

	def render(self, request):
		default = self.default
		if request.postpath:
			default = request.postpath

		fmt, nchan, rate = default
		nchan = int(nchan)
		rate = int(rate)
		try:
			request.setHeader('content-type',
			    self.getmimetype(fmt, nchan, rate))
		except (ValueError, AttributeError, KeyError), x:
			return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE,
			    'Unsupported Media Type', str(x)).render(request)

		#except AttributeError:
		#	return error.NoResource('Unknown audio format.').render(request)
		#except ValueError:
		#	return error.NoResource('Unknown channels (%s) or rate (%s).' % (`nchan`, `rate`)).render(request)
		#except KeyError:
		#	return error.ErrorPage(http.UNSUPPORTED_MEDIA_TYPE,
		#	    'Unsupported Media Type',
		#	    'No mime-type for audio format: %s.' %
		#	    `fmt`).render(request)

		if request.method == 'HEAD':
			return ''

		self.producerFactory(request, self.dev, 'r',
		    (self.getfmt(fmt), nchan, rate, True))

		# and make sure the connection doesn't get closed
		return server.NOT_DONE_YET

	synchronized = [ 'render' ]
threadable.synchronize(AudioResource)

class ossaudiodev_fmts:
	pass

for i in (k for k in dir(ossaudiodev) if k[:5] == 'AFMT_' and \
    isinstance(getattr(ossaudiodev, k), (int, long))):
	setattr(ossaudiodev_fmts, i, getattr(ossaudiodev, i))

class AudioSource(AudioItem):
	def __init__(self, *args, **kwargs):
		file = kwargs.pop('file')
		fargs = eval(open(file).read().strip(), { '__builtins__': {}, })
		#    'ossaudiodev': ossaudiodev_fmts })
		self.dev = fargs.pop('dev')
		default = fargs.pop('default')
		kwargs['content'] = AudioResource(self.dev, default)
		AudioItem.__init__(self, *args, **kwargs)

		if False:
			self.bitrate = bitrate

		self.url = '%s/%s' % (self.cd.urlbase, self.id)
		self.res = ResourceList()
		self.res.append(Resource(self.url, 'http-get:*:%s:*' %
		    kwargs['content'].getmimetype()))
		# XXX - add other non-default formats

def getfmtstrings(f):
	r = []
	for i in ( x for x in dir(ossaudiodev) if x[:5] == 'AFMT_' ):
		val = getattr(ossaudiodev, i)
		if val & f:
			f &= ~val
			r.append(i)

	while f:
		print f, f & -f
		r.append(f & -f)
		f ^= f & -f

	return r

def detectaudiosource(origpath, fobj):
	path = os.path.basename(origpath)
	ext = os.path.splitext(path)[1]
	if ext == '.asrc':
		return AudioSource, { 'file': origpath }

	return None, None
registerklassfun(detectaudiosource)

from zope.interface import implements
from twisted.internet.interfaces import IConsumer

class FileConsumer:
	implements(IConsumer)

	def __init__(self, fp):
		self.fp = open(fp, 'w')
		self.producer = None

	def registerProducer(self, producer, streaming):
		if self.producer is not None:
			raise RuntimeError('already have a producer')
		self.streaming = streaming
		self.producer = producer
		producer.resumeProducing()

	def unregisterProducer(self):
		if self.producer is None:
			raise RuntimeError('none registered')

		self.producer = None

	def write(self, data):
		self.fp.write(data)

if __name__ == '__main__':
	if False:
		i = ossaudiodev.open('/dev/dsp2', 'r')
		print getfmtstrings(i.getfmts())
		i.setparameters(ossaudiodev.AFMT_S16_BE, 2, 44100, True)

		print `i.read(16)`
	else:
		aplr = AudioPlayer('/dev/dsp2', 'r',
		    (ossaudiodev.AFMT_S16_BE, 2, 44100, True))
		file = FileConsumer('test.output')
		file.registerProducer(aplr, True)
		from twisted.internet import reactor
		reactor.run()