### jack_workers: worker functions for
### jack - extract audio from a CD and encode it using 3rd party software
### Copyright (C) 1999-2004  Arne Zellentin <zarne@users.sf.net>

### 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

import sndhdr
import signal
import string
import posix
import array
import fcntl
import wave
import time
import pty
import sys
import os

import jack_functions
import jack_ripstuff
import jack_helpers
import jack_targets
import jack_utils
import jack_tag

from jack_globals import *
from jack_helpers import helpers
from jack_init import F_SETFL, O_NONBLOCK

def default_signals():
    signal.signal(signal.SIGTERM, signal.SIG_DFL)
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    signal.signal(signal.SIGQUIT, signal.SIG_DFL)
    signal.signal(signal.SIGHUP, signal.SIG_DFL)
    signal.signal(signal.SIGWINCH, signal.SIG_DFL)

def start_new_process(args, nice_value = 0):
    "start a new process in a pty and renice it"
    data = {}
    data['start_time'] = time.time()
    pid, master_fd = pty.fork()
    if pid == CHILD:
        default_signals()
        if nice_value:
            os.nice(nice_value)
        os.execvp(args[0], [a.encode(cf['_charset'], "replace") for a in args])
    else:
        data['pid'] = pid
        if os.uname()[0] == "Linux":
            fcntl.fcntl(master_fd, F_SETFL, O_NONBLOCK)
        data['fd'] = master_fd
        data['file'] = os.fdopen(master_fd)
        data['cmd'] = args
        data['buf'] = ""
        data['otf'] = 0
        data['percent'] = 0
        data['elapsed'] = 0
        return data

def start_new_ripper(track, ripper):
    "start a new DAE process"
    helper = helpers[cf['_ripper']]
    cmd = string.split(helper['cmd'])
    args = []
    for i in cmd:
        if i == "%n": args.append(`track[NUM]`)
        elif i == "%o": args.append(track[NAME].decode(cf['_charset'], "replace") + ".wav")
        elif i == "%d": args.append(cf['_cd_device'])
        elif i == "%D": args.append(cf['_gen_device'])
        else: args.append(i)
    data = start_new_process(args)
    data['type'] = "ripper"
    data['prog'] = cf['_ripper']
    data['track'] = track
    return data

def start_new_encoder(track, encoder):
    "start a new encoder process"
    helper = helpers[cf['_encoder']]
    if cf['_vbr']:
        cmd = string.split(helper['vbr-cmd'])
    else:
        cmd = string.split(helper['cmd'])

    args = []
    for i in cmd:
        if i == "%r":
            args.append(`track[RATE] * helper['bitrate_factor']`)
        elif i == "%q":
            if helper.has_key('inverse-quality') and helper['inverse-quality']:
                quality = min(9, 10 - cf['_vbr_quality'])
            else:
                quality = cf['_vbr_quality']
            args.append("%.3f" % quality)
        elif i == "%i":
            args.append(track[NAME].decode(cf['_charset'], "replace") + ".wav")
        elif i == "%o":
            args.append(track[NAME].decode(cf['_charset'], "replace") + jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension'])
        else:
            if jack_targets.targets[helper['target']]['can_pretag']:
                if i == "%t":
                    if jack_tag.track_names:
                        args.append(jack_tag.track_names[track[NUM]][1])
                    else:
                        args.append("")
                elif i == "%a":
                    if jack_tag.track_names:
                        if jack_tag.track_names[track[NUM]][0]:
                            args.append(jack_tag.track_names[track[NUM]][0])
                        else:
                            args.append(jack_tag.track_names[0][0])
                    else:
                        args.append("")
                elif i == "%n":
                    args.append(`track[NUM]`)
                elif i == "%l":
                    if jack_tag.track_names:
                        args.append(jack_tag.track_names[0][1])
                    else:
                        args.append("")
                elif i == "%G":
                    if cf['_id3_genre'] >= 0: args.append(cf['_id3_genre'])
                    else: args.append('255')
                elif i == "%g":
                    if cf['_id3_genre'] >= 0: args.append(jack_tag.genretxt)
                    else: args.append('Unknown')
                elif i == "%y":
                    if cf['_id3_year'] > 0:
                        args.append(`cf['_id3_year']`)
                    else:
                        args.append('0')
                else:
                    args.append(i)
            else:
                args.append(i)
    data = start_new_process(args, cf['_nice_value'])
    data['type'] = "encoder"
    data['prog'] = cf['_encoder']
    data['track'] = track
    return data

def start_new_otf(track, ripper, encoder):
    "start a new ripper/encoder pair for on-the-fly encoding"
    data = {}
    data['rip'] = {}
    data['enc'] = {}
    data['rip']['otf'] = 1
    data['enc']['otf'] = 1
    enc_in, rip_out = os.pipe()
    data['rip']['fd'], rip_err = os.pipe()
    data['enc']['fd'], enc_err = os.pipe()
    args = []
    for i in string.split(helpers[ripper]['otf-cmd']):
        if i == "%n": args.append(`track[NUM]`)
        elif i == "%d": args.append(cf['_cd_device'])
        elif i == "%D": args.append(cf['_gen_device'])
        else: args.append(i)
    data['rip']['start_time'] = time.time()
    pid = os.fork()
    if pid == CHILD:
        default_signals()
        os.dup2(rip_out, STDOUT_FILENO)
        os.dup2(rip_err, STDERR_FILENO)
        os.close(rip_out)
        os.close(rip_err)
        os.execvp(args[0], args)
        # child won't see anything below...
    os.close(rip_out)
    os.close(rip_err)
    data['rip']['pid'] = pid
    data['rip']['cmd'] = helpers[cf['_ripper']]['otf-cmd']
    data['rip']['buf'] = ""
    data['rip']['percent'] = 0
    data['rip']['elapsed'] = 0
    data['rip']['type'] = "ripper"
    data['rip']['prog'] = cf['_ripper']
    data['rip']['track'] = track
    if cf['_vbr']:
        cmd = string.split(helpers[cf['_encoder']]['vbr-otf-cmd'])
    else:
        cmd = string.split(helpers[cf['_encoder']]['otf-cmd'])
    args = []
    for i in cmd:
        if i == "%r":
            args.append(`track[RATE] * helpers[cf['_encoder']]['bitrate_factor']`)
        elif i == "%q":
            if helper.has_key('inverse-quality') and helper['inverse-quality']:
                quality = min(9, 10 - cf['_vbr_quality'])
            else:
                quality = cf['_vbr_quality']
            args.append("%.3f" % quality)
        elif i == "%o":
            args.append(track[NAME].decode(cf['_charset'], "replace") + jack_targets.targets[jack_helpers.helpers[cf['_encoder']]['target']]['file_extension'])
        elif i == "%d":
            args.append(cf['_cd_device'])
        elif i == "%D":
            args.append(cf['_gen_device'])
        else:
            args.append(i)
    data['enc']['start_time'] = time.time()
    pid = os.fork()
    if pid == CHILD:
        default_signals()
        if cf['_nice_value']:
            os.nice(cf['_nice_value'])
        os.dup2(enc_in, STDIN_FILENO)
        os.dup2(enc_err, STDERR_FILENO)
        os.close(enc_in)
        os.close(enc_err)
        os.execvp(args[0], args)
        # child won't see anything below...
    os.close(enc_in)
    os.close(enc_err)
    data['enc']['pid'] = pid
    data['enc']['otf-pid'] = data['rip']['pid']
    data['enc']['cmd'] = cmd
    data['enc']['buf'] = ""
    data['enc']['percent'] = 0
    data['enc']['elapsed'] = 0
    data['enc']['type'] = "encoder"
    data['enc']['prog'] = cf['_encoder']
    data['enc']['track'] = track
    data['rip']['otf-pid'] = data['enc']['pid']

    if os.uname()[0] == "Linux":
        fcntl.fcntl(data['rip']['fd'], F_SETFL, O_NONBLOCK)
        fcntl.fcntl(data['enc']['fd'], F_SETFL, O_NONBLOCK)
    data['rip']['file'] = os.fdopen(data['rip']['fd'])
    data['enc']['file'] = os.fdopen(data['enc']['fd'])
    return data

def ripread(track, offset = 0):
    "rip one track from an image file."
    data = {}
    start_time = time.time()
    pid, master_fd = pty.fork() # this could also be done with a pipe, anyone?
    if pid == CHILD:
        #debug:
        #so=open("/tmp/stdout", "w")
        #sys.stdout = so
        #se=open("/tmp/stderr", "w+")
        #sys.stderr = se
        default_signals()

# FIXME: all this offset stuff has to go, track 0 support has to come.

        print ":fAE: waiting for status report..."
        sys.stdout.flush()
        hdr = sndhdr.whathdr(cf['_image_file'])
        my_swap_byteorder = cf['_swap_byteorder']
        my_offset = offset
        if hdr:

## I guess most people use cdparanoia 1- (instead of 0- if applicable)
## for image creation, so for a wav file use:

            image_offset = -offset

        else:
            if string.upper(cf['_image_file'])[-4:] == ".CDR":
                hdr = ('cdr', 44100, 2, -1, 16) # Unknown header, assuming cdr
#
## assume old cdrdao which started at track 1, not at block 0
                image_offset = -offset

            elif string.upper(cf['_image_file'])[-4:] == ".BIN":
                hdr = ('bin', 44100, 2, -1, 16) # Unknown header, assuming bin
#
## assume new cdrdao which starts at block 0, byteorder is reversed.
                my_swap_byteorder = not my_swap_byteorder
                image_offset = 0

            elif string.upper(cf['_image_file'])[-4:] == ".RAW":
                hdr = ('bin', 44100, 2, -1, 16) # Unknown header, assuming raw
                image_offset = 0

            else:
                debug("unsupported image file " + cf['_image_file'])
                posix._exit(4)
        
        expected_filesize = jack_functions.tracksize(jack_ripstuff.all_tracks)[CDR] + CDDA_BLOCKSIZE * offset
#
## WAVE header is 44 Bytes for normal PCM files...
#
        if hdr[0] == 'wav':
            expected_filesize = expected_filesize + 44

        if abs(jack_utils.filesize(cf['_image_file']) - expected_filesize) > CDDA_BLOCKSIZE:
            # we *do* allow a difference of one frame
            debug("image file size mismatch, aborted. %d != %d" % (jack_utils.filesize(cf['_image_file']), expected_filesize))
            posix._exit(1)

        elif hdr[0] == 'wav' and (hdr[1], hdr[2], hdr[4]) != (44100, 2, 16):
            debug("unsupported WAV, need CDDA_fmt, aborted.")
            posix._exit(2)

        elif hdr[0] not in ('wav', 'cdr', 'bin'):
            debug("unsupported: " + hdr[0] + ", aborted.")
            posix._exit(3)

        else:
            f = open(cf['_image_file'], 'r')
#
## set up output wav file:
#
            wav = wave.open(track[NAME].decode(cf['_charset'], "replace") + ".wav", 'w')
            wav.setnchannels(2)
            wav.setsampwidth(2)
            wav.setframerate(44100)
            wav.setnframes(0)
            wav.setcomptype('NONE', 'not compressed')
#
## calculate (and seek to) position in image file
#
            track_start = (track[START] + image_offset) * CDDA_BLOCKSIZE
            if hdr[0] == 'wav':
                track_start = track_start + 44
            f.seek(track_start)
#
## copy / convert the stuff
#
            for i in range(0, track[LEN]):
                buf = array.array("h")
                buf.fromfile(f, 1176) # CDDA_BLOCKSIZE / 2
                if not my_swap_byteorder:  # this is inverted as WAVE swabs them anyway.
                    buf.byteswap()
                wav.writeframesraw(buf.tostring())
                if i % 1000 == 0:
                    print ":fAE: Block " + `i` + "/" + `track[LEN]` + (" (%2i%%)" % (i * 100 / track[LEN]))
                    sys.stdout.flush()
            wav.close()
            f.close()

            stop_time = time.time()
            read_speed = track[LEN] / CDDA_BLOCKS_PER_SECOND / ( stop_time - start_time )
            if read_speed < 100:
                print "[%2.0fx]" % read_speed,
            else:
                print "[99x]",
            if hdr[0] in ('bin', 'wav'):
                print "[      - read from image -     ]"
            else:
                print "[cdr-WARNING, check byteorder !]"
            sys.stdout.flush()
            posix._exit(0)
    else: # we are not the child
        data['start_time'] = start_time
        data['pid'] = pid
        data['fd'] = master_fd
        data['file'] = os.fdopen(master_fd)
        data['cmd'] = ""
        data['buf'] = ""
        data['type'] = "image_reader"
        data['prog'] = "builtin"
        data['track'] = track
        data['percent'] = 0
        data['otf'] = 0
        data['elapsed'] = 0
    return data

