#!/usr/bin/perl
=head1 NAME

reencoder - wrapper to reencode audio to different bitrates and format

=head1 SYNOPSIS

reencoder [--help] | [--debug] [--verbose] [[--test] | [ [--ogg-to-mp3] |
[--mp3-to-ogg] | [--wma-to-mp3] | [--wma-to-ogg] | [--mp3-to-mp3]]
[--bitrate=BITRATE] [--outputdir=DIR]]


=head1 DESCRIPTION

"reencoder" reencodes audio to a different format, and optionally a different
bitrate.  It was originally designed to convert from ogg to mp3, but will also
convert from wma, and will convert to both ogg and mp3.

Use the --test option to check if the necessary applications are available.  Use
--help to get examples of how to use.

=head1 PREREQUISITES

You will need the libogg-vorbis-header-perl and libmp3-tag-perl packages (and
their dependencies) if using Debian/Ubuntu.  Everything else, grab the
appropriate modules (Ogg::Vorbis::Header and MP3::Tag) from CPAN.

=head1 BUGS

The bitrate may only be setting the minimum bitrate for lame, not the target
bitrate.

=head1 AUTHOR INFORMATION

Copyright 2005-2007, Michael Howe <michael@michaelhowe.org>.  All rights
reserved.

Originally written for Claire Millican.

This script is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

Questions and comments are welcome.

=over

=head1 FUNCTIONS (REFERENCE ONLY)

Below are general functions, as used in the script, for reference only.

=cut

use strict;
use warnings;
use Ogg::Vorbis::Header;
use MP3::Tag;
use IO::File;
use Getopt::Long;
use File::Basename;
# Where we save the intermediate wav files
my $tmpdir = "/tmp/oggconversion";

# Name of the script (used to prefix debugging output).
my $procname = 'reencoder';

# May be redefined by options passed to the script later.
my $help = 0;
my $debug = 0;
my $verbose = 0;
my $test = 0;
my $bitrate = 128;
my $oggmp3 = 1;
my $mp3ogg = 0;
my $wmamp3 = 0;
my $wmaogg = 0;
my $mp3mp3 = 0;
my $outputdir = `pwd`;

=item debug( @debugstring )

Takes an array @debugstring, which is printed to stderr if the verbose flag is
passed.

=cut

sub debug(@) {
	STDERR->print("$procname: ", @_, "\n") if ($verbose);
}

=item convert( $file, $cext, $next, $outputdir, $bitrate )

Converts the given file to the given format.
Accepts five arguments:
File to convert
Extension of file to convert
Extension of file to be create
Dir on which to create new file
Bitrate of file

=cut

sub convert($$$$$) {
	my $file = shift;
	my $cext = shift;
	my $next = shift;
	my $outputdir = shift;
	my $bitrate = shift;
	my $outname;
	my %comments = (
		artist => "",
		title => "",
		album => ""
	);
	
	# populate the comments hash:
	if ( 'ogg' eq $cext ) {
		my $ogg = Ogg::Vorbis::Header->new($file);
		%comments = (
			artist => $ogg->comment('artist'),
			title => $ogg->comment('title'),
			album => $ogg->comment('album')
		);
	} elsif ( 'mp3' eq $cext ) {
		my $mp3 = MP3::Tag->new($file);
		my ($title, $artist, $album) = ($mp3->autoinfo())[1,3,4];
		if ( $mp3->{ID3v2} ) {
			$title = ($mp3->{ID3v2}->get_frame("TIT2"))[0];
			$artist = ($mp3->{ID3v2}->get_frame("TPE1"))[0];
			$album = ($mp3->{ID3v2}->get_frame("TALB"))[0];
		} elsif ( $mp3->{ID3v1} ) {
			$title = $mp3->{ID3v1}->title;
			$artist = $mp3->{ID3v1}->artist;
			$album = $mp3->{ID3v1}->album;
		}
		%comments = (
			artist => $artist,
			title => $title,
			album => $album
		);
	}
	# we don't even like to _think_ about WMA files.

	# This just stops the warnings about empty values, as they may get
	# set to undef if there's no value (obviously).
	$comments{artist} = "" unless ( $comments{artist} ) ;
	$comments{album} = "" unless ( $comments{album} );
	$comments{title} = "" unless ( $comments{title} );

	print "Beginning encoding of $file\n";

	# Mplayer magic - check it exists, then encode the initial file to
	# a wav file.
	my $mplayer = searchpath('mplayer');
	debug "Using mplayer at $mplayer.";
	( $mplayer ) || die("Can't find mplayer.  Try --help.");
	my $basename = basename($file);
	$basename =~ s{\.$cext$}{.wav};
	debug "Checking for existance of temporary directory.";
	( -d $tmpdir ) || mkdir $tmpdir || die( "Can't make directory $tmpdir: $!");
	
	debug "Encoding $file to $tmpdir/$basename using mplayer";
	system "$mplayer/mplayer -vc dummy -vo null -quiet -ao pcm:file=\"$tmpdir/$basename\" \"$file\" 1>/dev/null 2>&1";
	debug "Finished mplayer encoding";

	# Sanity check - die if the file has not been created.

	(-f "$tmpdir/$basename" ) || die( "Error - encoding of file $file failed
($tmpdir/$basename does not exist).\nPlease try running the mplayer command
manually.\nCommand: mplayer -vc dummy -vo null -quiet -ao
pcm:file='$tmpdir/$basename' '$file'\n");


	# Check what format the output file should be, then encode
	# appropriately:
	if ( 'mp3' eq $next ) {
		$outname = $basename;
		$outname =~ s{\.wav$}{.mp3};
		# Whee!  Let's fire up lame:
		my $lame = searchpath('lame');
		( $lame) || die( "Error - you asked for mp3 encoding, but cannot find the lame binary in your path.  Please try --help.");
		debug "Encoding '$tmpdir/$basename' to '$outputdir/$outname' using lame";
		# Do the actual encoding:
		system "$lame/lame --quiet -h -b $bitrate --vbr-new --tt \"$comments{title}\" --ta \"$comments{artist}\" --tl \"$comments{album}\" --add-id3v2 \"$tmpdir/$basename\" \"$outputdir/$outname\"";
		debug "Encoding of '$outputdir/$outname' finished";
		# Clean up:
		debug "Removing $tmpdir/$basename";
		unlink "$tmpdir/$basename";
	} elsif ( 'ogg' eq $next ) {
		# We want an ogg file:
		$outname = $basename;
		$outname =~ s{\.wav$}{.ogg};
		my $oggenc = searchpath('oggenc');
		( $oggenc ) || die( "Error - you asked for ogg encoding, but cannot find the oggenc binary in your path.  Please try --help.");
		debug "Encoding '$tmpdir/$basename' to '$outputdir/$outname' using oggenc";
		system "$oggenc/oggenc --quiet --bitrate $bitrate --artist \"$comments{artist}\" --album \"$comments{album}\" --title \"$comments{title}\" --output=\"$outputdir/$outname\" \"$tmpdir/$basename\"";
		debug "Encoding of '$outputdir/$outname' finished";
		# Clean up:
		debug "Removing $tmpdir/$basename";
		unlink "$tmpdir/$basename";
	}
	print "File '$file' converted to '$outputdir/$outname'\n";
}

=item usage( $output_object )

Prints usage statement.
Accepts one argument, the thing that will be used to print the statement.

=cut

sub usage($) {
	my $f = shift;
	$f->print(<<EOF);
$0: Interconverts media files.
Note that these files are _lossy_, so the quality of the new file will be worse
than that of the original - there's nothing that can be done about this.

Options:
    --help	Display this message
    --verbose	Display more info on what the script is doing
    --test	Check that you have the appropriate programs installed
		(currently mplayer, lame and oggenc)
    --bitrate	The bitrate that you want to encode the ogg/mp3 at (default
    		128kB/s)
    --outputdir	The directory to save the new files to (defaults to current
    		directory).  Will attempt to create the directory if it doesn't
		exist
    --ogg-to-mp3 (default)
    		Convert from ogg to mp3
    --mp3-to-ogg
    		Convert from mp3 to ogg
    --wma-to-mp3
    		Convert from wma to mp3
    --wma-to-ogg
    		Convert from wma to ogg
    --mp3-to-mp3
		Convert from mp3 to mp3

Sample usage:
\$ cd /a/dir/with/ogg/files/
\$ mkdir mp3s
\$ $0 --outputdir=/a/dir/with/ogg/files/mp3s/ --ogg-to-mp3 *.ogg
Creates a mp3 copy in subdirectory /mp3s/ of all ogg files in
/a/dir/with/ogg/files.

To convert a wma file to a mp3 file:
\$ $0 --wma-to-mp3 /a/random/wma/file.wma
Creates a file called 'file.mp3' in the current directory from the wma file
'file.wma' in directory '/a/random/wma/'.

Copyright 2005 Michael Howe, written for Claire Millican.

EOF
}

=item dotest

Do some tests to see if appropriate apps are on the system

=cut

sub dotest() {
	# Firstly, mplayer:
	my $mplayer = searchpath('mplayer');
	if ( $mplayer ) {
		print "Mplayer found at $mplayer\n"
	} else {
		warn( "Can't find mplayer in your path - do you have it installed?\nMplayer is REQUIRED for this script to run.");
	}
	# lame:	
	my $lame = searchpath('lame');
	if ( $lame ) {
		print "lame (mp3 encoder) found at $lame\n";
	} else {
		warn( "lame (mp3 encoder) not found in your path - do you have it installed?\nlame or oggenc are required for this script." );
	}
	# oggenc:
	my $oggenc = searchpath('oggenc');
	if ( $oggenc ) {
		print "oggenc (ogg encoder) found at $oggenc\n";
	} else {
		warn("oggenc (ogg encoder) not found in your path - do you have it installed?\nlame or oggenc are required for this script.");
	}
	if ( ($oggenc) && ($lame) ) {
		print "Oggenc and lame found.  There should be no encoding problems.\n";
	} else {
		warn("Neither lame nor oggenc were found in your path - you will not be able to use this script.\n");
	}
}


=item searchpath( $searchstring )

Searches the PATH environment variable for the specified program.
Argument is the program to look for.
Returns the dir that it is in if it is found, 0 otherwise.

=cut


sub searchpath($) {
	my $forwhat = shift;
	my @path = split( /:/, $ENV{"PATH"} );

	my $foundit = 0;
	foreach (@path) {
#		debug "looking at $_/$forwhat";
		if ( -f "$_/$forwhat" ) {
			$foundit = $_;
			last;
		}
	}
	return $foundit;
}

# start general code
GetOptions(
	'help|h' =>	\$help,
	'debug|d' =>	\$debug,
	'verbose|v' =>	\$verbose,
	'test|t' =>	\$test,
	'ogg-to-mp3' =>	\$oggmp3,
	'mp3-to-ogg' =>	\$mp3ogg,
	'wma-to-mp3' =>	\$wmamp3,
	'wma-to-ogg' =>	\$wmaogg,
	'mp3-to-mp3' => \$mp3mp3,
	'bitrate=i' =>	\$bitrate,
	'outputdir=s' =>	\$outputdir
);

# Yeah, has stupid cr/lf/whatever from pwd
chomp $outputdir;

if ($help) {
	usage(\*STDOUT);
	exit(0);
}

if ($test) {
	dotest();
	exit(0);
}

(@ARGV > 0 ) || die "Error - no files specified!  Try --help for help.\n";

my @files = @ARGV;

my ($ext, $newext);
if ( $oggmp3 ) { $ext = "ogg"; $newext = "mp3"; }
if ( $mp3ogg ) { $ext = "mp3"; $newext = "ogg"; }
if ( $wmamp3 ) { $ext = "wma"; $newext = "mp3"; }
if ( $wmaogg ) { $ext = "wma"; $newext = "ogg"; }
if ( $mp3mp3 ) { $ext = "mp3"; $newext = "mp3"; }

foreach (@files) {
	chomp;
	unless (m{\.$ext$}) {
		debug "File $_ is not a $ext file.";
		next;
	}
	# Ok, we have a file to convert.  So what now?
	debug "Converting $_";
	convert($_, $ext, $newext, $outputdir, $bitrate);
}

=back

=cut

# vim: tw=80
