#!/usr/bin/env python
#
# part of
# re.codec is not a real codec
#
# restream - parses a ics (ical) file and switches
#            to another ogg stream if nessessary
#            ogg streams are provided by 
#							- live encoders (studioA,..)
#							- an archive (preproduced shows, repetitions)
#	
#	TODO:	
#		- think of some way for the end of the shows.
#
# (c) 2004 <j@reboot.fm>
#

import os,string
from sys import exit,stdout
from ogg import OggSyncState, vorbis
from shout import Shout,ShoutException
from types import NoneType
from urllib2 import urlopen
from math import *

# installed by hand right now. might put it in one package. python-pdi
from pdi.icalendar import VCalendar,ICalendar
import pdi.parser

# installed by hand right now. might put it in one package. python-dateutil
from dateutil.parser import *
import time
import datetime


class restreamogg :
	def __init__(self) :
		#some variables mostly urls
		#self.ics_url='http://meta.b.lab.net:9000/vcal/current.ics'
		self.ics_url='http://localhost/current.ics'
		self.fallback_url='file:///usr/local/share/reboot.fm/os.ogg'
		self.url=None
		
		# some stuff to handle the shout to the icecast server
		self.ogg = OggSyncState()
		self.page = 0
		self.serial = 1
		self.shout = Shout()
		self.shout.host = '127.0.0.1'
		self.shout.port = 8000
		self.shout.user = 'source'
		self.shout.password = 'hack4me'
		self.shout.mount = '/j_stream.ogg'
		self.shout.format = 'vorbis'
		self.shout.protocol = 'http'
		self.shout.open()
	def __call__(self, data) :
		self.ogg.bytesin(data)
		while 1:
				newpage = self.ogg.pageseek()
				if newpage == None :
					break
				if self.page == 0 :
				   	self.page = newpage
				   	continue
				if self.page.serialno != newpage.serialno : self.page.eos = 1
				if newpage.pageno==0 : self.page.eos = 1
				self.page.serialno=self.serial
				if self.page.eos == 1 :
				   if self.serial<65536 : self.serial = self.serial + 1
				   else : self.serial = 0
				self.shout.sync()
				out=self.page.header+self.page.body
				self.shout.send(out)
				self.page=newpage
		return

def fillbuffer(f):
	read = f.read(4096)
	length=len(read)
	return (read,length)

def print_log(string):
	print time.strftime("%Y-%m-%d %H:%M:%S"),"\t:",string
	stdout.flush()

def upcoming_show(so):
	shows= {}
	ics_fh=urlopen(so.ics_url)
	try:
		calendar = pdi.parser.fromFileObject(ics_fh, ICalendar())
	except:
		print_log("coudn't parse the ics file. please fix me!")
	ics_fh.close
	#<year><month><day>T<hour><minute<second><type designator>
	icaltimeformat_utc="%Y%m%dT%H%M%SZ"
	# its always localtime. timezones are stored in TZID not willing to
	# reimplement that here. still looking for a better ical lib.
	# or implememnt it in pdi so that DTSTART provides i.e. getContentAsTime()
	#<year><month><day>T<hour><minute<second>	
	icaltimeformat="%Y%m%dT%H%M%S"
	for show in calendar.components:
		try:
			next_show=parse(show.properties['DTSTART'].getContent().strip())
			next_show_end=parse(show.properties['DTEND'].getContent().strip())
		except:
			continue
		if next_show_end > datetime.datetime.now(next_show_end.tzinfo):
			shows[next_show]=show.properties['LOCATION'].getContent().strip()
		#print "%s: %s" % (next_show,shows[next_show])
		
	#return (next_show,shows[next_show])
	return shows

#initalize some variables,
now=0
f_tmp=0

print_log("starting up Ogg restreamer.")
while 1:
	try:
		so=restreamogg()
	except:
		print_log("couldn't connect to icecast server.")
		# what is a cood timeout here? to not try too often
		sleep(1)
		continue
	f=urlopen(so.fallback_url)
	print_log("fallback, open: %s" % so.fallback_url)
	while 1:
		length = 0
		while length == 0 :
			if now < int(time.time()):  #run only once a second
				now=int(time.time())
				next_shows = upcoming_show(so)
				if so.url == None:  #this is part of initializing
					print_log("getting the latest show (booting)")
					next_key=None
					for key in next_shows.keys():
						if next_key == None: next_key=key
						if key > next_key and key < now: next_key= key
					if next_key !=None:
						so.url=next_shows[next_key]
						try:
							print_log("latest show, open: %s" % so.url)
							f_tmp = urlopen(so.url)
						except:
							print_log("couldn't open stream: %s" % so.url)
						if f_tmp != 0:
							f.close()
							f=f_tmp
							f_tmp=0

			# now actually look if there is a show to start.
			# the update process has to go into another thread?
			# otherwise it could take longer than a second.
				now=int(time.time())
				if next_shows.has_key(now):
					if next_shows[now] != so.url: #we only want to switch if the url changes.
						try:
							print_log("new show, open: %s" % next_shows[now])
							f_tmp = urlopen(next_shows[now])
						except:
							print_log("couldn't open stream: %s" % next_shows[now])
						if f_tmp != 0:
							f.close()
							f=f_tmp
							f_tmp=0
							so.url=next_shows[now]
					else:
						print_log("new show, same url: %s" % so.url)
			(read,length) = fillbuffer(f)
			#error reading from url. try reconnect,
			#than switch to fallback_url
			if length == 0:
				f.close()
				try:
					f= urlopen(so.url)
					print_log("reconnect to stream: %s" % so.url)
				except:
					f.close()
					f= urlopen(so.fallback_url)
					print_log("fallback, open: %s" % so.fallback_url)
		
		try:
			so(read)
		except ShoutException:
		# so the connection to the icecast server is down.
		# clean up some open filehandles and reconnect
			f.close()
			break
	f.close()
