#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
recording2archive.py - cut out parts of an archive

 Copyright (C) 2004 jan gerber <j@reboot.fm>

 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Library General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

 TODO
  - the pageno of the first logical bitstream needs to be
		 fixed to avoid the impression of lost data.
	 - make a more general version that can act as oggcut input cut cut output
	 - make samples2seconds/seconds2samples use the samplerate of the stream

 2004-03-07 version 0.2 based on libogg2 and ogg-python2
'''

import sys
import string
from time import *
from sys import exit,argv
import os
from os import listdir,unlink,rename,walk,stat,rmdir
from os.path import abspath,exists,getsize,join,isdir,basename,split
import time
import datetime
from urllib2 import urlopen

# installed by hand right now. might put it in deb package. python-dateutil
from dateutil.parser import *
from dateutil.relativedelta import *

# this is the ogg-python2 cvs module from cvs.xiph.org
# needs the libogg2-zerocopy branch of libogg from cvs.
#
# svn co http://svn.xiph.org/trunk/ogg2/
# svn co http://svn.xiph.org/trunk/ogg-python2/
import ogg2

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

# installed by hand right now. might put it in deb package. python-dateutil
from dateutil.parser import *
from dateutil.relativedelta import *

# timebase_format is added to base
#base="/home/j/media/reboot.fm/rec/enc0der"
base="/mnt/t2/Reboot/recordings"
cutup_dir="/mnt/t2/Reboot/shows2archive"
timebase_format="%Y-%m-%d"
#timebase_format=""
file_time_format="%Y-%m-%d_%H.%M.%S.ogg"

def get_base(date):
	if(timebase_format):
		timebase=strftime(timebase_format,date)
	else:
		timebase=""
	return join(base,timebase)

def next_file(first_time,offset):
	best=3600
	new_file_time=mktime(first_time)+offset;
	file_time=localtime(new_file_time)
	for file in  listdir(get_base(file_time)):
		file_as_time=strptime(file,file_time_format)
		if abs(mktime(file_as_time)-mktime(file_time))< best:
			best=abs(mktime(file_as_time)-mktime(file_time))
			new_file=strftime(file_time_format,file_as_time)
	#new_file=strftime(file_time_format,file_time)
	return (file_time,join(get_base(file_time),new_file))

def find_first_file(rec_begin):
	begin_file_time=0
	for file in  listdir(get_base(rec_begin)):
		try:
			# fix case there show overlaps days
			file_time=strptime(file, file_time_format)
		except:
			continue
		if mktime(file_time) < mktime(rec_begin) and (begin_file_time==0 or mktime(file_time)> mktime(begin_file_time)):
			begin_file=file
			begin_file_time=file_time
	if begin_file_time==0:
		rec_begin_orig=rec_begin
		rec_begin=localtime(mktime(rec_begin)-3600)
		
		for file in  listdir(get_base(rec_begin)):
			try:
				# fix case there show overlaps days
				file_time=strptime(file, file_time_format)
			except:
				continue
			if mktime(file_time) < mktime(rec_begin) and (begin_file_time==0 or mktime(file_time)> mktime(begin_file_time)):
				begin_file=file
				begin_file_time=file_time
	return (begin_file_time,join(get_base(rec_begin),begin_file))

# highly dependent on ogg vorbis files for now
# should get the samplerate from header.
def seconds2samples(seconds):
	return seconds*44100

def samples2seconds(samples):
	return samples/44100

def safe_filename(string):
	string.replace("/","_")
	return string

def print_log(text):
	print text

def get_part_of_oggstream(rec_begin,rec_end,output_filename):
	file_start_time,filename=find_first_file(rec_begin)
	first_time=file_start_time
	print_log("output: %s" % output_filename)
	print_log("\t input: %s" % filename)
	f=file(filename,'r')
	syncin = ogg2.OggSyncState()
	syncout = ogg2.OggSyncState()
	outputfile=file(output_filename,'w')
	
	too_long=mktime(rec_begin)-mktime(file_start_time)
	total_length=too_long+(mktime(rec_end)-mktime(rec_begin))
	too_long = too_long - 1
	total_length_samples=seconds2samples(total_length)
	
	granulepos_samples=0
	first_header=0
	first_logical_bitstream=1
	new_pageno=0

	while granulepos_samples < total_length_samples:
		while syncin.input(f):
			while 1 :
				page = syncin.pageout()
				if page == None:
					break
				#new logical bytestream
				if page.bos:
					new_stream=1
				if page.eos and first_header==1 and first_logical_bitstream==1:
					first_logical_bitstream=0
				if page.bos and first_header==0:
					firststream_header = ogg2.OggStreamState(page.serialno)
					firststream_header.pagein(page)
				elif page.granulepos==0:
					if first_header==0:
						firststream_header.pagein(page)
					else:
						syncout.pagein(page)
						while syncout.output(outputfile) : pass
				else:
					if new_stream==1:
						new_stream=0
						#print_log("Compensating Granulepos: "+str(page.granulepos))
						#print_log("Compensating Seconds: "+str(samples2seconds(page.granulepos)))
						granulepos_start=page.granulepos
						granulepos_samples_last=page.granulepos
					else:
						granulepos_samples += page.granulepos-granulepos_samples_last
						granulepos_samples_last=page.granulepos
					if granulepos_samples > seconds2samples(too_long) and page != None:
						if first_header==0:
							first_header=1
							while 1:
								headerpage=firststream_header.pageout()
								if headerpage == None:
									break
								syncout.pagein(headerpage)
						if first_logical_bitstream==1:
							# open to rewrite, page.pageno has to be writeable
							# ask Arc how to do it or do it myself.
							# is further rewrite of header needed? granulepos anyone?
							'''
							new_pageno=new_pageno+1
							page.pageno=new_pageno
							'''
						syncout.pagein(page)
						while syncout.output(outputfile) : pass
			# stop if we are at the end.
			# need to set eos somehow(?)
			if granulepos_samples > total_length_samples:
				break
		# if we did not copy all we need. - switching to next file
		file_start_time,filename=next_file(first_time,samples2seconds(granulepos_samples))
		f.close()
		f=file(filename,'r')
		print_log("\t input: %s" % filename)	
	print_log("[length: %s]" % (strftime("%H:%M:%S",gmtime(samples2seconds(granulepos_samples)-too_long))))
	f.close()
	outputfile.close()

def ical2cmd(ical_filename,location_url):
	try:
		ics_fh=open(ical_filename)
	except:
		print_log("warning could not open file %s"%ical_filename)
		return None
	try:
		new_calendar = pdi.parser.fromFileObject(ics_fh, ICalendar())
	except:
		print_log("coudn't parse the ics file(%s). please fix me!"%ical_filename)
		ics_fh.close
		return None
	ics_fh.close
	# so the new ical file is valid
	for show in new_calendar.components:
		try:
			next_show=parse(show.properties['DTSTART'].getContent().strip())
			next_show_end=parse(show.properties['DTEND'].getContent().strip())
		except:
			continue
		fuzzy_time=2
		next_show = next_show + relativedelta(minutes=-fuzzy_time)
		next_show_end = next_show_end + relativedelta(minutes=+fuzzy_time)
		rec_begin=next_show.timetuple()
		
		
		#print rec_begin
		#print next_show.strftime("%Y-%m-%d_%H.%M.%S")
		#print mktime(rec_begin)
		#print 'localtime',localtime(mktime(rec_begin))
		
		rec_end=next_show_end.timetuple()
		
		
		title=safe_filename(show.properties['SUMMARY'].getContent().strip())
		
		output_filename=join(cutup_dir,"%s_%s.ogg" % 
				(next_show.strftime("%Y-%m-%d_%H.%M.%S"),
					show.properties['UID'].getContent().strip()))
		
		#now only archive shows marked to be archived
		#this could replace the current do not record Sendepause hack
		archive=False
		categories=show.properties['CATEGORIES'].getContent().strip().split("\,")
		location=show.properties['LOCATION'].getContent().strip()
		for categorie in categories:
			if categorie == 'archive' and location == location_url:
				archive=True
		if show.properties['SUMMARY'].getContent().strip() == 'Sendepause':
			archive=False
			
		if archive:
			try:
				get_part_of_oggstream(rec_begin,rec_end,output_filename)
				# rewrite the comments in the file.
				# since we still have more than one logical bitstream
				# it has to loop over all bitstreams and update the header.
				# should be integrated into the get_part_of_oggstream
				# for now write it to txt file.
				comment = "title=%s\n" % show.properties['SUMMARY'].getContent().strip().replace("\,",",")
				comment +="album=reboot.fm\n"
				comment += "uid=%s\n" % show.properties['UID'].getContent().strip()
				comment +="date=%s\n" % next_show.strftime("%Y-%m-%d %H:%M")
				cfile=open("%s.txt"%output_filename,"w")
				cfile.write(comment)
				cfile.close

			except:
				print_log("%s failed!"%output_filename);
	return None	
	
def get_new_ical_from_url(url,filename):
	#update ical file.
	#this needs to go somethere else
	t_filename="%s.t" %filename	
	try:
		ics_fh=urlopen(url)
	except:
		return None
	try:
		ics=ics_fh.read()
		t=open(t_filename,"w")
		t.write(ics)
		t.close()
	except:
		print_log("coudn't get the ical file")
		ics_fh.close
		return None
	ics_fh.close
	try:
		t=open(t_filename,"r")
		new_calendar = pdi.parser.fromFileObject(t, ICalendar())
		t.close
	except:
		print_log("coudn't parse the ics file.")
		return None
	# so the new ical file is valid
	t=open(filename,"w")
	t.write(ics)
	t.close()

# 1 min pro tag liegt ja nahe
# bei einer 90 min sendung
# dies hier haette noch den vorteil
# dass zu beginn viel begruesst
# und zum schluss viel verabschiedet wuerde
# und es ist mathematisch recht huebsch

def reboot_replay_recut():
	file_time_format="%Y-%m-%d_%H.%M.%S.ogg"
	start_date=parse("20040201T000000+2000")
	days=90
	samples=44100
	hours_per_day=18
	day_in_seconds=60
	total_length_of_cutup=days*60 # 90minutes
	length_of_one_hour=float(day_in_seconds)/hours_per_day
	
	increment = (float(3600)/days)/hours_per_day-(float(1)/day_in_seconds)
	#print increment
	#print length_of_one_hour
	day=0
	while day<days:
		hour=0
		while hour<24:
			if hour<6 or hour>=12:
				sec_start = (day * 18 + hour) * increment
				sec_end = sec_start + length_of_one_hour
				#to date
				begin=start_date+relativedelta(days=day,hours=hour,seconds=sec_start)
				end=start_date+relativedelta(days=day,hours=hour,seconds=sec_end)
				output_filename="%s/%s" %(cutup_dir,(start_date+relativedelta(days=day,hours=hour,seconds=sec_start)).strftime("%H/%H.%M.%S_%Y-%m-%d.ogg"))
				try:
					get_part_of_oggstream(begin.timetuple(),end.timetuple(),output_filename)
				except:
					print_log("%s failed!"%output_filename);
			hour +=1
		day +=1
	# der 90. tag wird natuerlich erst 6 std haben


if __name__ == '__main__':
	# test mode
	'''
	base="/tmp"
	cutup_dir="/tmp/cut"
	timebase_format="%Y-%m-%d"
	file_time_format="%Y-%m-%d_%H.%M.%S.ogg"
	rec_begin=strptime("2004-02-03_13.55.00.ogg",file_time_format)
	rec_end=strptime("2004-02-03_16.05.00.ogg",file_time_format)
	outpu_filename='log2.ogg'
	get_part_of_oggstream(rec_begin,rec_end,outpu_filename)
	'''
	# real mode
	if len(argv) == 2:
		if argv[1]=='yesterday':
			filename='/tmp/reboot.fm_yesterday.ics'
			end_date=strftime("%Y-%m-%d")
			start_date=strftime("%Y-%m-%d",(localtime(mktime(localtime())-86400)))
			url="http://www.reboot.fm/vcal/yesterday.ics?end_date=%s&start_date=%s" % (end_date,start_date)
			get_new_ical_from_url(url,filename)
			# set the location_url here to cut files form i.e. enc1
			#ical2cmd(filename,'http://rbfm.reboot.fm:8000/enc1.ogg')
			ical2cmd(filename,'')
		elif argv[1]=='reboot_replay_recut':
			cutup_dir="/mnt/t2/Reboot/reboot_replay_recut"
			reboot_replay_recut()
		else:
			ical2cmd(argv[1])
	else:
		print_log("you must provide ical file")
