#! /usr/bin/perl -w

# Program: Oggasm
# Version: 1.4.0
# Created: 4/20/01
# Last Modified: 2/17/02
# Written By: Sean Kellogg
# Lincensed under the GPL


# Modules
# -------

use MP3::Info;
use File::Copy;

# Intro and Startup Variables
# ---------------------------

print "\nOggasm v 1.4.\nThis program is licensed under the GPL and as such comes with NO WARRANTY!!!\n";

my %user; # this hash stores all the important data
$date = localtime;

# Start Log
# ---------

open REPORT, ">>$ENV{HOME}/oggasm.$date.log" or die "can't create log file: $!\n";
print REPORT "Oggasm Report Log: $date\n";

# Process command-line flags
# --------------------------

&checkflags(@ARGV);

# ask users questions about task
# ------------------------------

&getuserdata;

# create destination directory
# ----------------------------

print REPORT "\n--FOLDER CREATION--\n";

&createdestinationdir($user{'source'},$user{'destination'}) if ($user{'option'} =~ /3|4/);
print "file hierarchy created successfully!!\n" if ($user{'option'} =~ /3|4/);
print "no file hierachry required\n" if ($user{'option'} =~ /1|2/);

# begin file conversion
# ---------------------

print "Beginning Conversion...\n";
print REPORT "\n--FILE CONVERSION--\n";
&convertfile($user{'source'},$user{'destination'});


# finish log
# ----------

print REPORT "Program exited successfully\n\n";
close REPORT;

# terminate oggasm
# ----------------

print "A log of oggasm's activies can be found in $ENV{HOME}/oggasm.log\n";
print "Oggasm has exited succesfully\n\n";
exit;


# FUNCTIONS

# createdestinationdir
# ------------
# Recursive function that takes a folder to start copying from and a folder name to start creating at.
# The function will step through each directory and create it in same relative location as found in
# the starting directory.

sub createdestinationdir{
	my ($here,$there) = @_;
	chomp $there if $there=~/\//;
	chomp $here if $here=~/\//;
	$| = 1;
	print "Creating folder $there - ";

	if (!(-d $there)){
		print REPORT "Creating folder $there - ";
		mkdir($there) or die "Unable to create $there, you probably don't have write permissions\n";
	} else {
		print REPORT "Folder $there already exists - ";
	}
	print "Success!\n";
	print REPORT "success\n";
	$| = 0;
	opendir(DIR, $here) || die "can't opendir $here: $!";
	my @contents = readdir(DIR);
	closedir DIR;
	foreach (@contents){
		if (!($_=~/^\./)){
			if (-d "$here/$_"){
				if ("$here/$_" eq $user{'destination'}){
					print REPORT "Destination folder located - skipping\n";
				} else {
					createdestinationdir("$here/$_","$there/$_");
				}
			}
		}
	}


}

# convertfile
# -----------
# A recursive function that takes a directory to convert from and a directory to create at.  Note: the
# directories may be the same.  The function goes through the directory contents and stops at each file
# ending with the mp3 extension.  It will try to determine the artist and title from the ID3 tag, but
# will try and parse the file name if the ID3 Tag is unavailable.  The conversion is made via a system call.
# If the option is set, the mp3 will be deleted after conversion.  If the function encounters a directory, it
# will call itself passing the new folder.  If the option is set, the function will delete the folder
# once it has converted all the mp3s and no files are left in the folder.

sub convertfile{

	my ($here,$there) = @_;
	chomp $there if $there=~/\//;
	chomp $here if $here=~/\//;
	opendir(DIR, $here) || die "can't opendir $here: $!";
	my @contents = readdir(DIR);
	closedir DIR;
	foreach (@contents){
		if (!($_=~/^\./)){
			if (-d "$here/$_"){
				&convertfile("$here/$_","$there/$_");
			}
			if($_=~/\.$user{'convertfrom'}/){
				my $from = $_;
				my $to = $1 if ($from =~ /(.*)\./);
				my $fileName = $1;
				$to .= ".$user{'convertto'}";
				print "$from >> $to\n";
					my $title;
					my $artist;
					my $album;
					my $tracknum;
				if ($user{'convertfrom'} eq "mp3"){
					my $tag = get_mp3tag("$here/$from") ;
					$title = $tag->{TITLE};
					$artist = $tag->{ARTIST};
					$album = $tag->{ALBUM};
					$tracknum = $tag->{TRACKNUM};
				}

				# Step through the @format looking for metacharacters.  use regex to parse out the string
				for (my $i=1; $i < $#format; $i = $i+2){
					if ($format[$i] eq '%a' && !($artist)){
						my $pre = $format[$i-1];
						my $post = $format[$i+1];
						if ($post){
							$fileName=~/$pre(.*?)$post/;
							$artist = $1;
						}else{
							$fileName=~/$pre(.*)$post/;
							$artist = $1;
						}
						print REPORT "grabed artist name from file: $artist\n";
					}
					if ($format[$i] eq '%t' && !($title)){
						$pre = $format[$i-1];
						$post = $format[$i+1];
						if ($post){
							$fileName=~/$pre(.*?)$post/;
							$title = $1;
						}else{
							$fileName=~/$pre(.*)$post/;
							$title = $1;
						}
						print REPORT "grabed title name from file: $title\n";
					}
					if ($format[$i] eq '%n' && !($tracknum)){
						$pre = $format[$i-1];
						$post = $format[$i+1];
						if ($post){
							$fileName=~/$pre(.*?)$post/;
							$tracknum = $1;
						}else{
							$fileName=~/$pre(.*)$post/;
							$tracknum = $1;
						}
						print REPORT "grabed track number from file: $tracknum\n";
					}
					if ($format[$i] eq '%l' && !($album)){
						$pre = $format[$i-1];
						$post = $format[$i+1];
						if ($post){
							$fileName=~/$pre(.*?)$post/;
							$album = $1;
						}else{
							$fileName=~/$pre(.*)$post/;
							$album = $1;
						}
						print REPORT "grabed album name from file: $album\n";
					}

				}

				# BEHOLD!!!  THE MOTHER COMMAND!!
				# mpg123 sends out raw PCM data to standardout in quite mode, errors are sent to /dev/null.
				# The PCM data is piped to oggenc which has been niced.  oggenc is running in quite mode,
				# and excepting data via standard in.  the letter flags give instructions for the comment tag.
				# The last dash indicates using standard in.
				if ($user{'convertto'} eq "ogg"){
					$signal = system("mpg123 -sq \"$here/$from\" | nice -n \"$user{'nice'}\" oggenc -Q -r -b \"$user{'bitrate'}\" -o \"$there/$to\" -a \"$artist\" -t \"$title\" -N \"$tracknum\" -l \"$album\" -c  \"INFO: Coverted by Oggasm 1.4.0\" - ");
					&checkterminate("$signal");
				} elsif ($user{'convertto'} eq "ogg") {
					$signal = system("ogg123 -d wav -q \"$here/$from\"");
					&checkterminate("$signal");
					$signal = system("nice -n \"$user{'nice'}\" lame -S -b \"$user{'bitrate'}\" --ta \"$artist\" --tt \"$title\" --tn \"$tracknum\" --tl \"$album\" output.wav \"$there/$to\" 2> /dev/null");
					&checkterminate("$signal");
					unlink ("output.wav");
				} else {
					print "hmm...  oggasm doesn't know if your running in reverse mode or not.  That's both very odd and very bad.\nTerminating Oggasm\n";
					exit;
				}

				if (-s "$there/$to"){
					print REPORT "Converting $here/$from >> $there/$to: success\n";
					unlink ("$here/$from") if ($user{'option'} =~ /2|4/);
				}else{
					print REPORT "Converting $here/$from >> $there/$to: failure\n";
				}
			}
		}
	}
	if ($user{'option'} =~ /4/){
		rmdir($here);
	}
}

sub checkterminate{
	my ($signal) = @_;
	if (!($signal == 0)){
		print "User has terminated oggasm.\n";
		exit;
	}
}

sub getuserdata{

	&confirmapps;

	$user{'option'} = &setoption;
	$user{'source'} = &setlocal("source");
	if ($user{'option'} == 1 or $user{'option'} == 2){
		$user{'destination'} = $user{'source'};
	} else {
		$user{'destination'} = &setlocal("destination");
	}
	$user{'bitrate'} = &setbitrate;
	$user{'nice'} = &setnice;
	@format = &titleformat;

	&confirm;

}

sub confirm{
	print "
Confirm Action:
---------------
Convert all $user{'convertfrom'}s found in $user{'source'} to $user{'convertto'}s and save them in
$user{'destination'}.  Convert at $user{'bitrate'} kbs with a niceness of $user{'nice'}.\n\n";

	print "DO NOT " if ($user{'option'} == 1 or $user{'option'} == 3);
	print "DELETE ALL \U$user{'convertfrom'}s\E AFTER CONVERSION!!!\n";
	print "------------------------------------------\n";

	print "Yes[y]/Re-enter{r]/Quit[q]: ";
	chomp (my $input = <STDIN>);
	exit if ($input =~ /[Qq]/);
	&getuserdata if ($input =~ /[Rr]/);
	print "invalid option!\n" if (!($input =~ /[Yy]/));
	&confirm if (!($input =~ /[Yy]/));
}

sub confirmapps{

	# need to find a sutable free mp3 encoder
	$user{'lame'} = &checkprog("lame");
	$user{'oggenc'} = &checkprog("oggenc");
	$user{'ogg123'} = &checkprog("ogg123");
	$user{'mpg321'} = &checkprog("mpg321");

	if ($user{'mpg321'} eq "false" and $user{'convertto'} eq "ogg"){
		print "Oggasm requires mpg321 to run\n\n";
		exit;
	}
	if ($user{'oggenc'} eq "false" and $user{'convertto'} eq "ogg"){
		print "Oggasm requires oggenc to run\n\n";
		exit;
	}

	if ($user{'ogg123'} eq "false" and $user{'convertto'} eq "mp3"){
		print "Oggasm requires ogg123 to convert oggs into mp3s\n\n";
		exit;
	}
	if ($user{'lame'} eq "false" and $user{'convertto'} eq "mp3"){
		print "Oggasm requires lame to convert oggs into mp3s\n\n";
		exit;
	}
}

# checkprogs
# ----------
# check to see if a program is installed on the system

sub checkprog{
	($prog) = @_;
	my $check = system("which $prog > /dev/null");
	return "true" if ($check eq '0');
	return "false" if ($check);
}

# setoption
# ---------
# Asks user to chose between the 4 different run options.  Runs additional functions as necessary and
# sets the global $option.

sub setoption{

	print "
1: place $user{'convertto'}s in $user{'convertfrom'} folder - DO NOT DELETE \U$user{'convertfrom'}s\E
2: place $user{'convertto'}s in $user{'convertfrom'} folder - DELETE \U$user{'convertfrom'}s\E
3: place $user{'convertto'}s in separate $user{'convertto'} folder - DO NOT DELETE \U$user{'convertfrom'}s\E
4: place $user{'convertto'}s in separate $user{'convertto'} folder - DELETE \U$user{'convertfrom'}s\E
---------------------------------------------------------
Selection an option: ";
	chomp (my $option = <STDIN>);
	return $option;
}

# requestlocal
# ------------
# asks for either the source directory or destination directory. If source, it will check if the directory
# is there and ask for a new one if not.

sub setlocal{
	($what) = @_;
	my $root;
	if ($what eq "source"){
		print "Enter $user{'convertfrom'} directory to be converted: ";
		chomp ($root = <STDIN>);
		if (!(-d $root)){
			print "$root is not a valid directory!!\n";
			$root = &setlocal("source");
		}
	}
	if ($what eq "destination"){
		print "Enter $user{'convertto'} directory: ";
		chomp ($root = <STDIN>);
	}

	if ($root =~ /\/$/){
		chop($root);
	}

	return $root;
}

# setbitrate
# ----------
# asks user for a bitrate.  Uses 128 if there is no user input.  The function checks if the bitrate
# is to high or low, but doesn't bother to check if the value is one of the 6 standard because oggenc
# will approximate;

sub setbitrate{

	print "
The encoder can encode at an average bitrate of:
112, 128, 160, 192, 256, or 350.
------------------------------------------------\n";

	print "Enter desired bitrate or press enter for default [128]: ";
	chomp (my $bitrate = <STDIN>);
	$bitrate = 128 if !$bitrate;
	if (112 > $bitrate or 350 < $bitrate){
		print "$bitrate is not with the valid range!!\n";
		$bitrate = &setbitrate;
	}
	return $bitrate;
}

# setnice
# -------
# asks the user for a niceness value.  Checks if it is too high, too low, or a decimal.

sub setnice{
	print "
The encoder can run at different levels of niceness, which
will determine how much of the system resources it will consume.
Niceness can be an integer between 0 (runs with everything else)
and 20 (everything else takes precendence)
---------------------------------------------------------------\n";
	print "Enter desired niceness or press enter for default [0]: ";
	chomp (my $nice = <STDIN>);
	$nice = 0 if !$nice;
	if ($nice < -20 or $nice > 20 or $nice=~/\./){
		print "$nice is not a valid nice setting!!\n";
		$nice = &setnice;
	}
	return $nice;
}

# titleformat
# -----------
# prompts use to enter format style for thier mp3 file names so the program can parse out the title
# and artist incase the ID3 tag is gone.  Uses meta tags: %a=artist, %n=track number, %b=album, %t=title
# NOTE: if a user enters in characters used for regexing they will need to be escaped.

sub titleformat{
	print "
In case your $user{'convertfrom'}s do not have valid ID tags, Oggasm
will try and parse the file name to set the $user{'convertto'} tag.
If your format is like the default, press enter, otherwise enter
your style using the meta charcters
------------------------------------------------------------------\n";
	print 'Meta Characters: %a: artist  %t: title  %n: track number  %b: album',"\n";
	print 'Posible Example: (%n) - (%a) - (%t)',"\n";
	print "------------------------------------------------------------------\n\n";
	print "Enter your format or just press enter for default[\%a - \%t]: ";
	chomp (my $responce = <STDIN>);

	$responce = '%a - %t' if !($responce);
	my $i = 0;
	my @format;

	# split the user input into sections devided by the meta charcters.  This will be used to search the file
	# name strings later.
	while ($responce=~/%[atnb]{1}/){
		$format[$i] = $`;
		$format[$i+1] = $&;
		$responce = $';
		$i = ($i+2);
	}
	$format[$i] = $responce;

	# Escape the characters that are used in regexing
	foreach (@format){
		$_=~s/\(/\\\(/g;
		$_=~s/\)/\\\)/g;
		$_=~s/\[/\\\[/g;
		$_=~s/\]/\\\]/g;
		$_=~s/\*/\\\*/g;
		$_=~s/\$/\\\$/g;
		$_=~s/\./\\\./g;
	}
	@_ = @format;
}

# checkflags
# ----------
# takes all arguments passed in by through the command lind and checks them with regular
# expressions.  Adjusts user imput accordingly

sub checkflags {
	@ARGV = @_;

	$user{'convertto'} = "ogg"; #can be changed commandline option ONLY
	$user{'convertfrom'} = "mp3"; #can be changed commandline option ONLY
	foreach (@ARGV){
		# set the script to convert all mp3s to oggs
		if ($_ =~ /([reverse])/){
			$user{'convertto'} = "mp3"; #can be changed commandline option ONLY
			$user{'convertfrom'} = "ogg"; #can be changed commandline option ONLY
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1