#!/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_old():
    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=3240 # ~3 minutes per hour
    total_length_of_cutup=days*day_in_seconds
    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.%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


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=3240 # ~3 minutes per hour
    
    total_length_of_cutup=days*day_in_seconds
    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:
                piece=0
                while piece<12:
#                    sec_start = (day * 18 + hour) + (piece * 300) + 150
                    sec_start = (piece * 300) + 150
                    sec_end = sec_start + 14
                    #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.%M.%S_%Y-%m-%d.ogg"))
                    try:
                        get_part_of_oggstream(begin.timetuple(),end.timetuple(),output_filename)
                    except:
                        print_log("%s failed!"%output_filename);
                    piece +=1
            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

    cutup_dir="/mnt/t2/Reboot/doku3"
    reboot_replay_recut()
