#!/usr/bin/env python

from optparse import OptionParser
import sys, urllib, os, time, threading
import Timeparser

class Error(Exception):
	pass

class TryAgain(Error):
	pass

blkSize = 1024
_knownStreams = {
	'dlf': "http://www.dradio.de/streaming/dlf.m3u",
	'dradio': "http://www.dradio.de/streaming/dlr.m3u",
	'dlfogg': "http://www.dradio.de/streaming/dlf_mq_ogg.m3u",
	'dradioogg': "http://www.dradio.de/streaming/dkultur_mq_ogg.m3u",
}

class Encoder(threading.Thread):
	def __init__(self, bitrate, destFName, dataSource):
		threading.Thread.__init__(self, name="Encoder")
		self.encStream = os.popen("/usr/bin/lame --quiet --mp3input "
			"-m m -h -b %d - %s > /tmp/lame.msgs 2>&1"%(bitrate, destFName), "w")
		self.dataSource = dataSource
		self.wakeupCondition = threading.Condition()
	
	def getWakeupCondition(self):
		return self.wakeupCondition
	
	def run(self):
		self.wakeupCondition.acquire()
		try:
			streamClosed = False
			while not streamClosed:
				self.wakeupCondition.wait()
				while not streamClosed:
					newData = self.dataSource.read()
					if newData is None:
						streamClosed = True
					elif newData==0:
						break
					else:
						self.encStream.write(newData)
		finally:
			self.wakeupCondition.release()

	def shutdownEncoder(self):
		"""Has to be called from the main thread.  Sigh.
		"""
		try:
			self.encStream.flush()
			return self.encStream.close()
		except IOError, msg:
			sys.stderr.write("Lame process close crashed: %s\n"%str(msg))


class EncoderFromOgg(Encoder):
	def __init__(self, bitrate, destFName, dataSource):
		threading.Thread.__init__(self, name="EncoderFromOgg")
		self.encStream = os.popen("oggdec -Q -R -b16 -o- - |"
			" sox -t raw -r32 -s -w -c 2 - -t raw -c 1 - |"
			' lame --quiet -r -x -s32 -b %d -mm -h - "%s" '
			" > /tmp/lame.msgs 2>&1"%(bitrate, destFName), "w")
		self.dataSource = dataSource
		self.wakeupCondition = threading.Condition()


def getMp3Url(m3uUrl):
	m3uUrl = _knownStreams.get(m3uUrl, m3uUrl)
	return urllib.urlopen(m3uUrl).read().strip()


class FiFo:
	def __init__(self):
		self.data = []
		self.lock = threading.RLock()
		self.consumerCondition = None
	
	def read(self):
		self.lock.acquire()
		if self.data:
			readData = self.data.pop(0)
		else:
			readData = 0 
		self.lock.release()
		return readData
	
	def write(self, newData):
		self.lock.acquire()
		self.data.append(newData)
		self.lock.release()
		self.wakeupConsumer()
	
	def close(self):
		self.write(None)

	def wakeupConsumer(self):
		self.consumerCondition.acquire()
		if self.consumerCondition:
			self.consumerCondition.notify()
		self.consumerCondition.release()

	def setNotify(self, consumerCondition):
		self.consumerCondition = consumerCondition
	

def doEncoding(opts, args):
	srcStream = urllib.urlopen(getMp3Url(args[1]))
	encodingClass = {
		"mp3": Encoder,
		"ogg": EncoderFromOgg,
		"x-mp3": Encoder,
		"mpeg": Encoder,
		}[srcStream.info().getsubtype()]
	fifo = FiFo()
	endTime = time.time()+timeToRec
	firstblock = srcStream.read(blkSize)
#	if not firstblock.startswith("\xff\xf3"):
#		# Gruesome hack to catch bad stream
#		if firstblock.startswith("<!DOCTYPE"):
#			sys.stderr.write("Server full.  Waiting half a minute.\n")
#			time.sleep(30)
#		else:
#			pos = firstblock.find("\xff\xf3")
#			if pos==-1:
#				pos = firstblock.find("\xff\xd9")
#			if pos!=-1:
#				firstblock = firstblock[pos:]
#			else:
#				sys.stderr.write("Bad data received: %s\n"%repr(firstblock))
#				return 1

	enc = encodingClass(opts.rate, args[2], fifo)
	enc.start()
	fifo.setNotify(enc.getWakeupCondition())
	fifo.write(firstblock)
	while time.time()<endTime:
		fifo.write(srcStream.read(blkSize))
	fifo.close()
	enc.join(20)
	if enc.shutdownEncoder() is not None:
		sys.stderr.write("Encoder failed somehow.  Messages follow.\n")
		try:
			sys.stderr.write(open("/tmp/lame.msgs").read())
		except IOError:
			pass
		return 1
	return 0

if __name__=="__main__":
	parser = OptionParser(usage="%proc [options] time-to-record m3u-url"
		" destination-file")
	parser.add_option("-b", "--a4", type="int", dest="rate", default=24)
	opts, args = parser.parse_args()
	if len(args)!=3:
		parser.print_help()
		sys.exit(1)
	timeToRec = Timeparser.TimeParser(args[0]).getSeconds()
	startTime = time.time()
	for i in range(3):
		if doEncoding(opts, args):
			# Something went wrong.  Retry if there's not too much time
			# passed
			if time.time()-startTime<300:
				sys.stderr.write("Taking a second chance...\n")
				continue
		break
	else:
		sys.stderr.write("Server gave 3 bad answers in a row. Giving up.\n")
