#!/usr/bin/perl

=head1 NAME

mp3burn - burn audio CDs from MP3 or Ogg Vorbis files

=cut

use MP3::Info;
use Getopt::Std;
use File::Basename;


sub get_audio_info {
    my  ($filename) =@_;
    my $hash = MP3::Info::get_mp3info($filename);
    if (defined($hash)) {
        if ($have_mpg123) {
	    $hash->{DECODER} = ["mpg123-oss", "--rate", "44100", "--stereo", "-s", "-q"];
	} else {
	    if ($hash->{FREQUENCY} != 44.1) {
	       print "*** unable to continue ***\n";
	       print "mpg321 cannot handle files with sample rates <> 44.1kHz\n";
	       print "sample rate is $hash->{FREQUENCY} for file: $filename\n";
	       print "please either burn with this file or install mpg123\n";
	       &Cleanup;
	       exit 1;
	    }
  	    $hash->{DECODER} = ["mpg321", "--rate", "44100", "--stereo", "-s", "-q"];
	}    
    } else {
	$hash = {};
	my @vars = qx(ogginfo "$filename");
	for (@vars) {
	    m((?xg) ^  ([^=]+) = (.*) $ );
	    $hash -> {$1} = $2;
	}
	
	if (exists($hash->{playtime})) {
	    $hash->{DECODER}=["ogg123", "-d", "raw", "-f", "-", "-q"];
	    $hash->{TIME}=$hash->{playtime};
	    $hash->{SECS}=$hash->{length};
	    $hash->{MODE}=$hash->{channels};
	} else {
	    $hash = undef;
	}
    }
    $hash;
}


sub Cleanup {

   # kill off any children that might still be around
   if (@childern) {
      print "cleaning up children: @children\n";
      kill 'TERM', @children;
   }
   
   # remove any fifos we created
   if (@fifo) {
      unlink @fifo;
   }
}

=head1 SYNOPSIS

B<mp3burn> [B<-c> I<MMM:SS>] [B<-d>] [B<-m>] [B<-t> I<tmpdir>] [B<-o> I<cdrecord_opts>] [I<mp3 files>]

=cut


getopts('c:dt:o:mD', \%opts);


=head1 DESCRIPTION

B<mp3burn>
is a simple command line tool for making audio CDs from
MP3s without filling up your disk with .wav files.  It uses
Perl(1), mpg321(1) or mpg123(1), cdrecord(1), and the 
L<MP3::Info(3)> Perl module.

=cut

=head1 OPTIONS

=over 4

=cut

$DEBUG = $opts{'D'};
$dummy = $opts{'d'};

=item B<-m>

Manual C<cdrecord -swab> option mode.  
Use this to disable the automatic detection for swab mode in case it is
not working correctly on your system.  (Also, please send email to
<tmancill@debian.org> or file a bug against the L<mp3burn> package if you
encounter this problem.)

=back

=cut

$manual_cdrecord_opts = $opts{'m'};



=item B<-t> I<tmpdir>

Put temporary files in F<tmpdir>.
Default is to use the current directory.

=cut

if (length($opts{'t'}) > 1) {
	$tmpdir=$opts{'t'}."/";
	die "Cannot write to temp. dir -> $tmpdir" unless  ( -d $tmpdir &&  -w $tmpdir);
}



=item B<-c> I<MMM:SS>

Time check:  compute the total length of files to be burned
and warn if greater than
I<MMM:SS> minutes and seconds.

=cut

if (length($opts{'c'}) > 1) {
	die "Time check not available without MP3::Info module" if $no_mp3info;
	die "Time check needs to be in the form of MMM:SS" unless ($opts{'c'} =~ /\d{0,3}\:\d{2}$/);
	($min,$sec)=split(/\:/,$opts{'c'});
}


#Below is what the README told you to change.
#$cdrecord_opts = "-v dev=1,0 speed=4 -pad -swab -audio";

if (-r "$ENV{'HOME'}/.mp3burnrc") {		#process ~/.mp3burnrc
	open(RC, "$ENV{'HOME'}/.mp3burnrc");
	$oldRS = $/;
	undef $/;
	$rc = <RC>;
	close(RC);
	unless(defined eval $rc) {
		die "Error in .mp3burnrc:\n$@";
	}
	$/ = $oldRS;
}
$cdrecord_opts = $opts{'o'} if $opts{'o'};	# -o overrides .mp3burnrc

# this is no longer necessarily a condition to die...
#if ($cdrecord_opts eq '') {
#	die "Need to specify cdrecord options through -o or .mp3burnrc\n" .
#		"Usage:  mp3burn [-c MMM:SS] [-d] [-t tmpdir] [-o cdrecord_opts] [mp3 files]\n";
#}


=item B<-d>

Perform a "dummy" run: do everything except actually burn the CD
(uses L<cdrecord(1)> C<-dummy> option).

=cut

if ($dummy) {
   $cdrecord_opts .= " -dummy";
}   


=item B<-o> I<"> I<cdrecord_opts> I<">

Specify the command line options for cdrecord. 
The quotes are required to prevent B<mp3burn> from parsing cdrecord(1) options.
Overrides options specified in F<~/.mp3burnrc>.

Example: B<-o> I<"-v dev=1,0 speed=4 -swab">

=item B<Note:>

The options I<-pad> and I<-audio> are added automatically, since they are
always necessary.  The script also tries to detect if I<-swab>  is needed (for
example on x86 and other little-Endian platforms).  cdrecord is supposed
to take care of any byte-ordering requirements specific to your burner. 
(If you end up with a CD that merely sounds like static, you most likely
need to toggle use of I<-swab>.)  You should also consider using I<-v> so
that you can watch the burn in progress. This goes for F<~/.mp3burnrc> also.

=cut

$cdrecord_opts .= " -pad -audio";

unless ($manual_cdrecord_opts) {
   # if the datastream is in little-endian order, we need to 
   # add the swab flag to cdrecord if it's not already present

   if (!($cdrecord_opts =~ /.*-swab.*/)) {
      # assert: swab wasn't set
      # check to see if it's needed
      chop ($arch = `/bin/arch`);
      if ($DEBUG) { print "arch=$arch\n"; }

      if ($arch =~ /i[3456]86/) {	# ia32 - we need to swab
	 $cdrecord_opts .= " -swab";
         if ($DEBUG) { print "-swab flag automatically added\n"; }
      } #elsif () {}			# what other arch's need this?
   }
}

if ($#ARGV < 0) {				#give usage if no args
	die "Usage:  mp3burn [-c MMM:SS] [-d] [-t tmpdir] [-o cdrecord_opts] [mp3 files]\n";
}


# check to see if mpg123 is present 
#
# since this package depends on mpg321, we can count on 
# /usr/bin/mpg123 being a link to /etc/alternatives and then mpg321
# by default - for the time being check for the debian install of
# mpg123 in mpg123-oss <grumble>

if (-x "/usr/bin/mpg123-oss") {
   $have_mpg123 = "TRUE";
}

for($i = 0; $i <= $#ARGV; $i++) {		
	die "$ARGV[$i] does not exist" unless (-f $ARGV[$i]); #Check to see if file exists
	$fifo[$i] = $tmpdir . basename $ARGV[$i];	#set the names of the fifos
	$fifo[$i] =~ s/$/cdr/i;		#foo.mp3 -> foo.mp3.cdr
	if (-l $ARGV[$i]) {		#mp3info doesn't work on symlinks
	    $file = readlink $ARGV[$i];
	} else {
	    $file = $ARGV[$i];
	}
	$info = get_audio_info $file; #Let's get the mp3's time
	if ($sec) {	
		$totsecs += $info->{SECS} + 2; # total time + 2 for padding
	}
	system "mkfifo", $fifo[$i];	#Make our fifos (optionally to the tempdir)
 	# 2000/11/21 <tmancill@debian.org>
	# beef up the fork() code - example taken from camel book
	FORK:
        if ($pid = fork) {
           # we're in the parent here, child pid in $pid
           # we could use a list, but we're lazy and know how many procs
           # there will be, so use an array
           push @children, $pid;
        } elsif (defined $pid) {
           # if $pid is defined, it's == 0
           #start decoder processes
	   close(STDOUT);
	   open(STDOUT, ">$fifo[$i]");	#this to avoid using the shell
	   exec(@{$info->{DECODER}},  $ARGV[$i]);
	   die "Failed to exec \`".join(" ",@{$info->{DECODER}})."\': $!";
	} elsif ($! =~ /No more process/) {
           # EAGAIN, supposedly recoverable fork error
           sleep 5;
           redo FORK;
        } else {
           # weird fork error
           die "Can't fork: $!\n";
        }
}

$totmin=int $totsecs/60;
$totsec=$totsecs % 60;
if (($totsecs > (($min*60)+$sec)) && $sec) {
	print "The max time allocated was [$min:$sec].\n";
	printf "The total time came to [$totmin:%02d]\n", $totsec;
	print "Do you wish to continue? (Y/N) ";
	while (1) {
		$key=uc(getc);
		if ($key eq 'N') {
			unlink @fifo;
   			print "cleaning up children: @children\n";
   			kill 'TERM', @children;
			exit 1;
		}
		last if ($key eq 'Y');
	}
}
if ($sec){
	printf "Total time is [$totmin:%02d]\n", $totsec;
	sleep 3;
}
 
# escape the fifo names for the shell - this is overkill,
# but easy to add stuff to later
foreach $burnfile (@fifo) {
   $burnfile =~ s/"/\\"/g;
   $burnfile =~ s/\$/\\\$/g;
   $burnfiles .= " \"$burnfile\"";
}

# prepare the command line
$cdrecordcmd = "cdrecord " . join (" ", split(/\s+/, $cdrecord_opts)) . $burnfiles;

if ($DEBUG) { 
   print "invoking cdrecord with:\n";
   print "$cdrecordcmd\n";
}

# burn!
$rc = system "$cdrecordcmd";

# check the return code from cdrecord
if ($rc != 0) {
   # cdrecord exited non-zero
   print "warning:  cdrecord exited non-zero!\n";
}

&Cleanup;   
   
exit 0;

=head1 RETURN VALUE

B<mp3burn> returns 0 on success.

=head1 DIAGNOSTICS

=over

=item Error in .mp3burnrc:

Perl(1) cannot parse the F<.mp3burnrc> file.
The following example occurs when a double quote is not terminated.

  bash-2.05$ sudo mp3burn -d ~/bell.ogg 
  String found where operator expected at (eval 10) line 7, at end of line
	  (Missing operator before ?)
  Error in .mp3burnrc:
  Can't find string terminator '"' anywhere before EOF at (eval 10) line 7.
  bash-2.05$ 


=back

=head1 EXAMPLES

Write an Ogg Vorbis file from a CD-R drive, F</dev/scd0>, 
mounted at F</mnt/scd0/> to a 
CD-RW drive, F</dev/scd1>, called C<0,1,0> in cdrecord(1) SCSI notation.
Ensure that file is no longer than 50 minutes.
L<sudo(1)> is used to get root permissions for cdrecord(1).

  % sudo mp3burn -c 050:00 -o "-v speed=2 dev=0,1,0" /mnt/scd0/bell.ogg

Create a F<~/.mp3burnrc> that prints a message before writing.

  # This is an example.
  $cdrecord_opts="-v speed=2 dev=0,1,0" ;

  print "Nine seconds to slap a CD-R in the drive!\n" ;

  #
  # See mp3burn(3).
  #

=head1 FILES

=over 4

=item F<~/.mp3burnrc>

In this file, you may permanently specify the cdrecord options
you want to use.  The format is
C<$cdrecord_opts = "cdrecord options">.

You may place comments in this file by beginning a line with C<#>.

=item B<Note:>

The contents of F<~/.mp3burnrc> are ignored if the C<-o> command-line option
is used.

=back

=head1 CAVEATS

Has not been tested extensively with Ogg Vorbis files.
Ogg Vorbis files must be in CD-DA format: 
ie 44 100 sample/s/channel x 16 bits/sample x 2 channel.

=head1 BUGS

If you execute B<mp3burn> with root permissions, 
the F<~/.mp3burnrc> will also be executed with root permissions.

=head1 NOTES

There are a number of GUI frontends for B<mp3burn>:

=over

=item Xmp3Burn 
  
http://perso.wanadoo.es/ja_recio/xmp3burn/xmp3burn.html

=item Kmp3burn

http://computer.freepage.de/kmp3burn/index.htm

=item GtkMp3Burn

http://gtkmp3burn.sourceforge.net/

=back

=head1 SEE ALSO

cdrecord(1), mpg321(1), ogg123(1), ogginfo(1), L<MP3::Info(3)>
The B<mp3burn> web page is http://mp3burn.sourceforge.net/.
The Ogg Vorbis web page is http://www.xiph.org/ogg/vorbis/.

=head1 AUTHOR

Copyright (c) 2000 Ryan Richter. 

This script was written by Ryan Richter <bobort@bigfoot.com> with much code
contributed by Dan Lark <dlark@spinn.net>.

I would like to thank Dan Lark <dlark@spinn.net> for
contributing the ideas and code for most of the new features,
and Tony Mancill <tony@mancill.com> for making Debian packages
and helping with debugging.

This program is licensed under the GNU General Public License.
You may fold, spindle, and mutilate this software under the terms of the GPL.

=head1 HISTORY

=over

=item  20010917	<tmancill@debian.org>	

added check to automatically add -swab on ia32 platform

L<MP3::Info(3)> replaces MPEG::MP3Info

=item B<mp3burn> 0.02 10/28/00

Changes since 0.01:

Bugfixes:

Spaces, quotes, and other shell metacharacters in filenames
are no longer problematic.

Mono MP3s and MP3s not sampled at 44.1kHz are no longer
problematic.

New Features:

Editing the executable is no longer necessary; cdrecord
options can be specified on the command line or in F<~/.mp3burnrc>.

Temp dir for FIFOs may be set to other than the current dir.

Dummy runs now supported from the command line.

A time check is now available: B<mp3burn> can abort if total
time exceeds a threshold.  Requires L<MPEG::MP3Info>.

=back

=cut

