#!/usr/bin/python2.3

#
#   Copyright (c) 2001-2002 Alexander Leidinger. All rights reserved.
#
#   Redistribution and use in source and binary forms, with or without
#   modification, are permitted provided that the following conditions
#   are met:
#
#   1. Redistributions of source code must retain the above copyright
#      notice, this list of conditions and the following disclaimer.
#   2. Redistributions in binary form must reproduce the above copyright
#      notice, this list of conditions and the following disclaimer in the
#      documentation and/or other materials provided with the distribution.
#
#   THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
#   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
#   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
#   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
#   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
#   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
#   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
#   SUCH DAMAGE.
#

# $Id: encode,v 1.4 2004/06/15 22:03:06 duane Exp $

# slame:
# quick hack to test some aspects of pylame, a cleanup is needed

# TODO:
#  * merge use_gui and have_gtk
#  * split into multiple files
#  * cleanup
#
# CHANGES by One Louder/Lindows.com
#  * rename to 'encode', as part of Lsongs
#  * removal of all GUI code

import os, sys, getopt
import wave, aifc, sunau
import lame

	
def usage():
	print 'Usage: encode [-b <bitrate>] [-achqv] <infile> [<outfile>]' + os.linesep + \
		'   -a: produce an ABR MP3'                    + os.linesep + \
		'       -b: use bitrate of <bitrate> kbps'         + os.linesep + \
		'       -c: produce a CBR MP3'                     + os.linesep + \
		'       -h: print this help'                       + os.linesep + \
		'       -q: be quiet (no output except on errors)' + os.linesep + \
		'       -v: produce a more verbose output'         + os.linesep + \
		'   * per default a 128kbps CBR MP3 is created'    + os.linesep + \
		'   * if no <outfile> is given, encode tries to guess a reasonable one'


def set_parameters(mp3, nchannels, samplerate, nframes, bitrate, vbr):
	mp3.set_num_channels(nchannels)
	mp3.set_in_samplerate(samplerate)
	mp3.set_num_samples(long(nframes))

	mp3.set_vbr(vbr)
	if 8 <= bitrate <= 320:
		#print bitrate
		mp3.set_bitrate(bitrate)
	mp3.set_preset(bitrate)
	return mp3

def get_defaults():
	# defaults:
	bitrate = 128 #lame.PRESET_STANDARD
	verbose = 0
	quiet   = 0
	vbr     = lame.VBR_OFF #lame.VBR_DEFAULT
	return bitrate, verbose, quiet, vbr,


def process_options_or_exit(arglist):
	try:
		optlist, args = getopt.getopt(arglist, 'ab:cghqv')
	except getopt.GetoptError:
		usage()
		sys.exit(1)

	bitrate, verbose, quiet, vbr = get_defaults()

	# process options
	for opt, val in optlist:
		if opt == '-a':
			vbr     = lame.VBR_ABR
			continue
		if opt == '-b':
			bitrate = int(val)
			#print "got bitrate",bitrate
			continue
		if opt == '-c':
			vbr     = lame.VBR_OFF
			continue
		if opt == '-h':
			usage()
			sys.exit(0)
			continue            # can't happen, but makes the editor happy
		if opt == '-q':
			quiet = 1
			continue
		if opt == '-v':
			verbose = 1
			continue

	# check number of remaining arguments
	if (1 > len(args)) | (2 < len(args)):
		usage()
		sys.exit(1)

	in_file  = os.path.normcase(os.path.normpath(os.path.expanduser(args[0])))
	if 2 == len(args):
		mp3_name = os.path.normcase(os.path.normpath(os.path.expanduser(args[1])))
	else:
		root, extension = os.path.splitext(in_file)
		mp3_name = root + '.mp3'
	
	return args, bitrate, verbose, quiet, vbr, in_file, mp3_name


def open_soundfile_or_exit(file):
	try:
		sound = wave.open(file, 'rb')
	except wave.Error, wave_errval:
		try:
			sound = aifc.open(file, 'rb')
		except aifc.Error, aifc_errval:
			try:
				sound = sunau.open(file, 'rb')
			except sunau.Error, sunau_errval:
				print 'Unknown file format:' + os.linesep + \
					  ' AIFC  :', aifc_errval,  os.linesep, \
					  ' SUN AU:', sunau_errval, os.linesep, \
					  ' WAVE  :', wave_errval,  os.linesep
				sys.exit(1)
				  
	return sound


def is_readable_or_exit(file):
	if 1 != os.access(file, os.R_OK):
		print 'File "' + file + '" not readable.'
		sys.exit(1)


def get_average(bitrate_hist, frames_processed):
	average = float(0)
	for i in range(0, 14):
		if 0 != bitrate_hist[i]["value"]:
			average += bitrate_hist[i]["bitrate"] \
				* bitrate_hist[i]["value"] \
				/ float(frames_processed)
	return average


def print_stats(mp3, processed_bytes, raw_size, verbose, vbr, bitrate):
	global actual_bitrate, actual_progress, last_bitrate, last_progress, \
		   data_lock, data_event, event_timeout
	
	bitrate_hist = mp3.get_bitrate_histogram()
	stmode_hist  = mp3.get_bitrate_stereo_mode_histogram()
	frames_processed = mp3.get_frame_num()
	frames_total     = mp3.get_total_frames()
	stmode_total     = mp3.get_stereo_mode_histogram()

	if lame.VBR_DEFAULT == vbr:
		average_rate = get_average(bitrate_hist, frames_processed)
	else:
		average_rate = bitrate
	
	progress = frames_processed/float(frames_total)
	
	if 1 == verbose:
		print ''
	
	if lame.VBR_DEFAULT == vbr:
		print '|%d|%d|' % (
			  processed_bytes, raw_size)
	else:
		# ABR & CBR
		print '|%d|%d|' % (
			  processed_bytes, raw_size )
	
	sys.stdout.flush()
		  
	if 1 == verbose:
		print ''
		for i in range(0, 14):
			print '%3d: %4d (%5.1f%%)  LR: %4d LR-I: %4d MS: %4d MS-I: %4d' % (
			    bitrate_hist[i]["bitrate"], bitrate_hist[i]["value"],
			    100*bitrate_hist[i]["value"]/float(frames_total),
			    stmode_hist[i]["LR"], stmode_hist[i]["LR-I"],
			    stmode_hist[i]["MS"], stmode_hist[i]["MS-I"] )

		print 'Total: %4d of %4d frames (%5.1f%%)  LR: %4d LR-I: %4d MS: %4d MS-I: %4d' % (
		  frames_processed, frames_total, progress*100,
		  stmode_total["LR"], stmode_total["LR-I"],
		  stmode_total["MS"], stmode_total["MS-I"] )


def main():
	global actual_bitrate, actual_progress, last_bitrate, last_progress, \
		   data_lock, abort, data_event
	
	# argument parsing
	args, bitrate, verbose, quiet, vbr, in_file, mp3_name = \
		process_options_or_exit(sys.argv[1:])

	is_readable_or_exit(in_file)

	sound = open_soundfile_or_exit(in_file)
	
	# get some info about the infile
	nchannels, sampwidth, samplerate, nframes, comptype, compname = \
		sound.getparams()

	raw_size = nchannels * sampwidth * nframes

	if 1 == verbose & 0 == quiet:
		print "File      :", in_file
		print "nchannels :", nchannels
		print "sampwidth :", sampwidth
		print "samplerate:", samplerate
		print "nframes   :", nframes
		print "comptype  :", comptype
		print "compname  :", compname
		print "raw size  :", raw_size

		print os.linesep + "Lame:"
		print "URL      :", lame.url()
		print "Version  :", lame.version()

	if 2 != sampwidth:
		print 'Sorry, no support for != 16bit samples.'
		sys.exit(1)

	# mp3file
	mp3 = lame.init()
	mp3_file = open(mp3_name, "wb+")
	
	mp3 = set_parameters(mp3, nchannels, samplerate, nframes, bitrate, vbr)
	mp3.init_parameters()

	abort = 0

	num_samples_per_enc_run = samplerate

	# 1 sample = 2 bytes
	num_bytes_per_enc_run = nchannels * num_samples_per_enc_run * sampwidth
	
	processed_bytes = 0
	while 0 == abort:
		frames = sound.readframes(num_samples_per_enc_run)
		condition = num_bytes_per_enc_run != len(frames)
		if 1 == condition:
			abort = condition
		if 2 == nchannels:
			data = mp3.encode_interleaved(frames)
			mp3_file.write(data)
		else:
			print 'only 2 channels at the moment!'
			sys.exit(1)

		processed_bytes += len(frames)
	
		if 0 == quiet:
			print_stats(mp3, processed_bytes, raw_size, verbose, vbr, bitrate)

	data = mp3.flush_buffers()
	mp3_file.write(data)

	mp3.write_tags(mp3_file)

	if 0 == quiet:
	    print_stats(mp3, processed_bytes, raw_size, verbose, vbr, bitrate)

	mp3_file.close()
	mp3.delete()
	sound.close()

	
if __name__ == '__main__':
	main()
