#!/usr/bin/env python
#
# Copyright (C) 2004 Harang Camille <mammique@lamenagerie.com>
#
# 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 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.

# Added by mammique 25/07/2002
# the GNU General Public License http://www.gnu.org/licenses/gpl.txt or COPYING file

import commands, time, os, threading, re, signal, sys, tty, termios, string
from optparse import OptionParser


print "Parachutte, careful On-the-Fly\n"


modes = {'Default' :
         {'desc' : 'XviD 25% PAL',
          'd' : '-o',
          'o' : 'capture.avi',
          'e' : 'mencoder',
          'a' : '-cache 4096 -vop scale=384:288 -oac lavc -lavcopts acodec=mp3 -ovc xvid -',
          'c' : '64',
          't' : '10'
          }
         }

cfiles = ['/etc/parachutte', '/local/etc/parachutte',
          (os.path.expanduser('~/.parachutte'))]

for cfile in cfiles:
    if os.path.isfile(cfile):
        f = file(os.path.expanduser('~/.parachutte'))
        lines = f.readlines()
        for line in lines:
            line = line.split('#')[0]
            line = line.replace("\n", '')
            line = line.split(':')
            if len(line) >= 3:
                line = [line[0], line[1], string.join(line[2:], ':')]
                if not modes.get(line[0]):
                    modes[line[0]] = {}
                modes[line[0]][line[1]] = line[2]


oparser = OptionParser(version="version 0.1")

oparser.add_option('-o', '--output-file', dest='file', default=modes['Default']['o'],
                   help='target capture filename, default is ' +  modes['Default']['o'],
                   metavar='FILE')
oparser.add_option('-d', '--filename-delimiter', dest='delimiter',
                   default=modes['Default']['d'],
                   help='filename delimiter to pass --output-file to encoder \
                   default is ' + modes['Default']['d'],
                   metavar='FILE')
oparser.add_option('-e', '--encoder', dest='enc', default=modes['Default']['e'],
                   help='DV stream stdin encoder, default is ' + modes['Default']['e'],
                   metavar='ENC')
oparser.add_option('-a', '--encoder-args', dest='enc_args', default=modes['Default']['a'],
                   help='arguments to pass to encoder, default is "' \
                   + modes['Default']['a'] + '"', metavar='ENC_ARGS')
oparser.add_option('-c', '--cache', dest='cache', default=modes['Default']['c'],
                   help='cache size in MB, default is ' + modes['Default']['c'],
                   metavar='CACHE')
oparser.add_option('-t', '--tolerance', dest='tol', default=modes['Default']['t'],
                   help='amount limit for maximum and minimum buffer feeding \
                   before stoping or playing VCR, in MB, default is ' \
                   + modes['Default']['t'], metavar='TOL')
oparser.add_option('-m', '--mode', dest='mode', default='Default',
                   help='predefined mode configuration, see your config file. \
                   Type "--mode help" to list available modes')

options, args = oparser.parse_args()

if options.mode == 'help':
    for mode in modes:
        if mode != 'Default':
            m = mode + ' : '
            if modes[mode].get('desc'):
                m = m + modes[mode]['desc']
            print m
            for option in modes[mode]:
                if option != 'desc':
                    print "    -%s : %s" % (option, modes[mode][option])
            print
    sys.exit()

elif modes.get(options.mode):
    print options.ensure_value('o')
#    for option in modes[options.mode]:
#        print oparser.get_option('-' + option)
#    if options.delimiter !: modes['Default']['d']:
#        options.delimiter = ['d']
#          'o'
#          'e'
#          'a'
#          'c'
#          't'
sys.exit()


cache = int(options.cache) * 1024
tol = int(options.tol) * 1024
enc_args = options.enc + ' ' + options.enc_args + ' ' + options.delimiter + ' ' + options.file
print enc_args
enc_args = enc_args.split()


def Getch():

    """ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/134892 """

    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)

    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)

    return ch


class PipeFrame1394(threading.Thread):

    def __init__(self, o, i):

        threading.Thread.__init__(self)
        self.o = o
        self.i = i
        self.alive = True

    def run(self):
        while self.alive:
            self.i.write(self.o.read(144000))

    def stop(self):
        self.alive = False
        time.sleep(1 / 25.0)

    def restart(self):
        self.alive = True
        self.run()

    def close(self):
        self.stop()
        self.o.close()
        self.i.close()


class Superviser(threading.Thread):

    global tol, cache

    def __init__(self, bfr_e, dv_bfr, bfr_men):

        threading.Thread.__init__(self)
        self.bfr_e = bfr_e
        self.dv_bfr = dv_bfr
        self.bfr_men = bfr_men
        self.bufmem = 0.0
        self.feeding = True
        self.capturing = True
        self.events = UserEvents()
        self.events.start()
        self.event = ''
        self.verbadd = ''
        if not commands.getstatusoutput('dvcont play')[0] == 0:
            self.verbadd = 'playing failed'
        else:
            self.verbadd = "Escape to stop capture"

    def run(self):

        l = ''
        r = ''
        re_mem = re.compile(".*k/s-> *([0-9.]+)").match
        re_l = re.compile(".*(\[.*\]\[.*\]+)(.*)").match
        self.alive = True

        while self.alive:

            if not bfr_e.closed:
                r = r + self.bfr_e.readline(80)
                r = r.replace("\x08", '')
                if re_l(r):
                    l = re_l(r).group(1)
                    if len(re_l(r).groups()) == 2:
                        r = re_l(r).group(2)
                    else:
                        r = ''
                    l_fail = 0
                else:
                    l_fail = l_fail + 1
                if l_fail >= 3:
                    l = 'k/s-> 0.0'

            if l != '':
                if re_mem(l):
                    self.bufmem = float(re_mem(l).group(1))
                    self.verb()
                    self.event = self.events.read()
                    self.check()
                l = ''
            else:
                time.sleep(0.1)

    def verb(self):

        l = 42
        lbufmem = int(self.bufmem / cache * l)
        j = '=' * (lbufmem - 1)
        if not self.feeding or not self.capturing:
            j = j + '<'
        else:
            j = j + '>'
        j = j + (' ' * (l - lbufmem))
        j = '[' + j + ']'
        sys.stdout.write("\r%s %10sk %30s" % (j, str(self.bufmem), self.verbadd))
        sys.stdout.flush()

    def check(self):

        if self.event:
            
            if ord(self.event) == 27 and self.capturing:
                self.dv_bfr.close()
                self.verbadd = 'capture ended'
                if commands.getstatusoutput('dvcont stop')[0] == 0:
                    self.capturing = False

        if not self.capturing and self.bufmem == 0:
            self.bfr_men.close()
            self.bfr_e.close()
            self.events.alive = False
            self.alive = False
            print 'Done.'

        elif (self.bufmem >= cache - tol):
            if commands.getstatusoutput('dvcont stop')[0] == 0:
                self.feeding = False
                self.verbadd = 'stop'

        elif (self.bufmem <= tol) and not self.feeding:
            if commands.getstatusoutput('dvcont play')[0] == 0:
                self.feeding = True
                self.verbadd = 'play'


class UserEvents(threading.Thread):

    def __init__(self):
        threading.Thread.__init__(self)
        self.alive = True
        self.buf = ''

    def run(self):
        while self.alive:
            ch = Getch()
            self.buf = self.buf + ch

    def read(self):
        if self.buf != '':
            ret = self.buf[0]
            self.buf = self.buf[1:]
        else:
            ret = ''
        return ret


dv_o = file('/dev/dv1394')
bfr_i, bfr_o, bfr_e = os.popen3(('bfr', '-b', str(cache) + 'k', '-p'))
men_i, men_o, men_e = os.popen3(enc_args) ; men_o.close()


dv_bfr = PipeFrame1394(dv_o, bfr_i)
dv_bfr.start()

bfr_men = PipeFrame1394(bfr_o, men_i)
bfr_men.start()

Superviser(bfr_e, dv_bfr, bfr_men).start()
