#!/usr/bin/python

# GST_DEBUG=dvb*:3 python recorder.py 
import os
import copy

import pygst
pygst.require('0.10')
import gst
import gobject

import kaa.notifier
kaa.notifier.init('gtk')

import kaa.ioctl as ioctl

DVB_S = 'DVB-S'
DVB_C = 'DVB-C'
DVB_T = 'DVB-T'

class DVB(object):

    def __init__(self, adapter):

        # read frontend0 for aditional information
        frontend = '/dev/dvb/adapter%s/frontend0' % adapter
        INFO_ST = '128s10i'
        val = ioctl.pack( INFO_ST, "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 )
        devfd = os.open(frontend, os.O_TRUNC)
        r = ioctl.ioctl(devfd, ioctl.IOR('o', 61, INFO_ST), val)
        os.close(devfd)
        val = ioctl.unpack( INFO_ST, r )
        self.name = val[0].strip()
        self.type = (DVB_S, DVB_C, DVB_T)[val[1]]

        # create pipeline
        self._pipeline = gst.Pipeline()
        self._pipeline.get_bus().add_watch(self._bus_event)
        self._dvbbin = gst.element_factory_make("dvbbasebin")
        self._dvbbin.set_property('adapter', adapter)
        self._dvbbin.connect("pad-added", self._pad_added)
        self._dvbbin.connect("pad-removed", self._pad_removed)
        self._pipeline.add(self._dvbbin)
        self._streams = {}
        self._tuning_data = None
        

    def add(self, channel, sink):
        # get tuning data and tune to specific frequency
        tuning_data, access_data = channel.get_tuning_data(self.type)
        if self._tuning_data != tuning_data:
            # FIXME: does not work when already tuned!
            print '>>> tune to', tuning_data
            for var, value in tuning_data.items():
                self._dvbbin.set_property(var, value)
            self._tuning_data = tuning_data
        sid = str(access_data.get('sid'))
        
        # start pipeline
        if not self._streams:
            self._pipeline.set_state(gst.STATE_PLAYING);

        # create pipeline (tee) for channel if needed
        if not sid in self._streams:
            print 'create sid pipeline'
            tee = gst.element_factory_make('tee')
            tee.set_state(gst.STATE_PLAYING)
            self._pipeline.add(tee)
            self._streams[sid] = ( tee, [] )
            self._dvbbin.set_property('program-numbers', ':'.join(self._streams.keys()))

        # add sink to sid pipeline
        print 'start', sid, sink
        sink.set_state(gst.STATE_PLAYING)
        self._pipeline.add(sink)
        tee, streams = self._streams[sid]
        pad = tee.get_request_pad('src%d')
        pad.link(sink.get_pad('sink'))
        streams.append(sink)


    def remove(self, channel, sink):
        sid = str(channel.get_tuning_data(self.type)[1].get('sid'))
        pad = sink.get_pad('sink').get_peer()
        tee, streams = self._streams[sid]
        # remove sink from pipeline and tee
        print 'remove', sink
        streams.remove(sink)
        sink.set_state(gst.STATE_NULL)
        self._pipeline.remove(sink)
        pad.unlink(sink.get_pad('sink'))
        tee.remove_pad(pad)
        if len(streams):
            return
        print 'last sink, remove sid', sid
        del self._streams[sid]
        self._dvbbin.set_property('program-numbers', ':'.join(self._streams.keys()))
        tee.set_state(gst.STATE_NULL)
        self._pipeline.remove(tee)
        if len(self._streams.keys()):
            return
        print 'last channel, stop dvbbin'
        # FIXME: that blocks!
        # self._pipeline.set_state(gst.STATE_PAUSED)


    def _bus_event(self, bus, message):
        t = message.type
        if t == gst.MESSAGE_ERROR:
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            return True
        if t == gst.MESSAGE_STATE_CHANGED:
            if message.parse_state_changed()[1] == gst.STATE_PLAYING:
                print '%s PLAYING' % message.src
            if message.parse_state_changed()[1] == gst.STATE_NULL:
                print '%s STOPPED' % message.src
            return True
        print message
        return True


    def _pad_added(self, bin, pad):
        sid = pad.get_name().split('_')[-1]
        print self._streams
        print 'PAD:', pad
        print 'PADNAME:',pad.get_name()
        print 'PADCAPS:',pad.get_caps()
        print 'SINK:', self._streams[sid]
        pad.link(self._streams[sid][0].get_pad('sink'))
        return True


    def _pad_removed(self, bin, pad):
        print 'STOP', pad, pad.get_peer()


class Stream(object):

    def __init__(self, channel, sink):
        self._sink = sink
        self._channel = channel
        
    def start(self, device):
        device.add(self._channel, self._sink)
        self._device = device
        
    def stop(self):
        self._device.remove(self._channel, self._sink)
        self._device = None
        self._sink = None
        

class Recording(Stream):

    def __init__(self, channel, filename):
        sink = gst.element_factory_make('filesink')
        sink.set_property('location', filename)
        super(Recording,self).__init__(channel, sink)


class Channel(dict):
    def __init__(self, name):
        super(Channel,self).__init__()
        self.name = name

    def add_tuning_data(self, type, tuning_data, access_data):
        self[type] = tuning_data, access_data

    def get_tuning_data(self, type):
        return self.get(type)

class ChannelList(dict):
    
    def read_dvbt_channels_conf(self, filename):
        BANDWIDTH = [ '8', '7', '6', 'AUTO' ]
        GUARD = [ '32', '16', '8', '4', 'AUTO' ]
        CODE = [ '1_2', '2_3', '3_4', '4_5', '5_6', '6_7', '7_8', '8_9', 'AUTO' ]
        QUAM = [ 'QPSK', '16', '32', '64', '128', '256', 'AUTO' ]
        TRANS = [ '2K', '8K', 'AUTO' ]
        HIERARCHY = [ 'NONE', '1', '2', '4', 'AUTO' ]
        INVERSION = [ 'OFF', 'ON', 'AUTO' ]
        for line in open(filename).readlines():
            name, frequency, inversion, bandwidth, code_rate_lq, code_rate_hq, \
                  quam, transmission_mode, guard, hierarchy, \
                  pid1, pid2, sid = line.strip().split(':')
            tuning_data = {
                'frequency': int(frequency),
                'bandwidth': BANDWIDTH.index(bandwidth.split('_')[1]),
                'guard': GUARD.index(guard.split('_')[-1]),
                'code-rate-lp': CODE.index(code_rate_lq[4:]),
                'code-rate-hp': CODE.index(code_rate_hq[4:]),\
                'modulation': QUAM.index(quam[4:]),
                'trans-mode': TRANS.index(transmission_mode.split('_')[-1]),
                'hierarchy': HIERARCHY.index(hierarchy.split('_')[-1]),
                'inversion': INVERSION.index(inversion.split('_')[-1])
            }
            if not name in self:
                self[name] = Channel(name)
            self[name].add_tuning_data(DVB_T, tuning_data, dict(sid=sid))

    def read_dvbs_channels_conf(self, filename):
        for line in open(filename).readlines():
            name, frequency, polarity, unknown, symbol_rate, \
                  pid1, pid2, sid = line.strip().split(':')
            tuning_data = {
                'frequency': int(frequency) * 1000,
                'polarity': polarity,
                'symbol_rate': int(symbol_rate)
            }
            if not name in self:
                self[name] = Channel(name)
            self[name].add_tuning_data(DVB_S, tuning_data, dict(sid=sid))

cl = ChannelList()
cl.read_dvbt_channels_conf('/home/dmeyer/channels.conf')

dvb = DVB(adapter=0)
r1 = Recording(cl.get('Das Erste'), 'ARD.ts')
r2 = Recording(cl.get('Das Erste'), 'ARD2.ts')
r3 = Recording(cl.get('hr-fernsehen'), 'hr.ts')

kaa.notifier.OneShotTimer(r1.start, dvb).start(0)
kaa.notifier.OneShotTimer(r2.start, dvb).start(2)
kaa.notifier.OneShotTimer(r3.start, dvb).start(10)
kaa.notifier.OneShotTimer(r1.stop).start(12)
kaa.notifier.OneShotTimer(r2.stop).start(14)
kaa.notifier.OneShotTimer(r3.stop).start(20)
kaa.notifier.OneShotTimer(kaa.notifier.shutdown).start(25)

kaa.notifier.loop()


