#!/usr/bin/perl

# Copyright 1996 Spy Networking.
#
# This program is distributed as-is, no warranties expressed or
# implied.  If you are afraid it will damage your computer or anything
# else, you very well could be right.  It's up to you to decide.  I
# don't think it'll damage anything, but I can only promise you that
# my computer still works and this program works on it.
#
# Summary:
# If it breaks, you get to keep both halves.
#
# Please direct any questions or comments to dustin@spy.net


# Want more information than necessary?
$debug=0;
$random=0;
$verbose=1;
$play=1;
$cddev="/dev/cdrom";

# get database name
if(defined($ENV{CDTHINGDB}))
{
	$database=$ENV{CDTHINGDB};
}
else
{
	$database="$ENV{HOME}/.cdthing";
}

@id=split(' ', '$Id: cdthing,v 1.4 1996/01/05 00:25:33 dustin Exp $');

# Struct formats (kinda)
$tochdr_t="CC";
$tocentry_t="CCCCCCC CCCCC";
$subch_t="CCCCC CCCC CCCC CCC";
$ti_t="CCCC";

# includes
require 'linux/cdrom.ph';

# messages
@status=("Invalid", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"playing","paused",
"completed", "error", "Stopped");

# global variables:
#
# @nameinfo: Names of stuff from the database
#
# @shuffled: the shuffled array created by &shuffle
#
# @status: array containing messages to print for the return value of
#          subchannel ioctl call
#
# @subch: subchannel information
#
# $tracks: number of tracks

sub usage
{
	print <<EOF;
cdthing $id[2] by Dustin Sallings
cdthing -[dDeElPqrRsSv] [start [stop]]
where option is one or more of the following:
-d	increase debugging level
-D	delete current cd
-e	edit database entry
-E	eject cd
-l	don't play, just list
-P	print database
-q	decrease verbosity
-r	play one random track
-R	play the whole CD randomly
-s	Show CD status
-S	Stop the CD
-v	increase verbosity

or

cdthing -p track ...

to play a program track

EOF
}

# This is just the sorting method...
sub byartist
{
	return(($cdinfo{$a} cmp $cdinfo{$b}));
}

# Prints all of the junk in the cd database
sub printdata
{
	local(%cdinfo, @blah, $key);

format top=
Artist                      CD name
--------------------------------------------------------------------------
.

format STDOUT=
@<<<<<<<<<<<<<<<<<<<<<<<    @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$blah[0]                    $blah[1]
.

	dbmopen(%cdinfo, "$database", 0644);

	for $key (sort byartist keys(%cdinfo))
	{
		@blah=split("\0", $cdinfo{$key});

#		print "Artist: $blah[0]\nCD:\t\t$blah[1]\n";
		write;
	}

	dbmclose(%cdinfo);
}

# deletes the current cd from the database
sub deletecd
{
	local(%cdinfo, $key, @info, @cdinfo);

	dbmopen(%cdinfo, "$database", 0644);

	@info=&getcdinfo();

	for(@info)
	{
		($min, $sec)=split(':');
		$sec=($min*60)+$sec;
		push(@cdinfo, $sec);
	}

	$key=join('\0', @cdinfo);
	@oldinfo=split("\0", $cdinfo{$key});

	if(!defined($cdinfo{$key}))
	{
		print "That CD's not in the database\n";
		dbmclose(%cdinfo);
		return(0);
	}

	print "Title: $oldinfo[1]\nArtist: $oldinfo[0]\n";
	print "Are you sure you want to delete this?\n";

	$_=<STDIN>;
	if(/y/i)
	{
		print "Deleting...\n";
		delete($cdinfo{$key});
	}

	dbmclose(%cdinfo);
	return(0);
}

# edits the current CD
sub edit
{
	local(%cdinfo, $key, $track, @info, @ninfo, $info, @cdinfo);

	dbmopen(%cdinfo, "$database", 0644);

	@info=&getcdinfo();
	@ninfo=@info;
	for(@info)
	{
		($min, $sec)=split(':');
		$sec=($min*60)+$sec;
		push(@cdinfo, $sec);
	}

	reset @info;
	@info=();

	$key=join('\0', @cdinfo);
	@oldinfo=split("\0", $cdinfo{$key});

	print "Who is this CD by?";
	print "\t[", $oldinfo[0], "]" if(defined($oldinfo[0]));
	print "\n";
	$_=<STDIN>;
	chop;
	if($_ eq "")
	{
		push(@info, $oldinfo[0]);
	}
	else
	{
		push(@info, $_);
	}
	print "What's the name of the CD?";
	print "\t[", $oldinfo[1], "]" if(defined($oldinfo[1]));
	print "\n";
	$_=<STDIN>;
	chop;
	if($_ eq "")
	{
		push(@info, $oldinfo[1]);
	}
	else
	{
		push(@info, $_);
	}
	shift(@oldinfo); shift(@oldinfo);

	for $track (1..$#cdinfo)
	{
		print "$track:\t", &fixtime(&lengthof($ninfo[$track-1], $ninfo[$track]));
		print "\t\[$oldinfo[$track-1]\]" if(defined($oldinfo[$track-1]));
		print "\n";
		$_=<STDIN>;
		chop;
		if($_ eq "")
		{
			push(@info, $oldinfo[$track-1]);
		}
		else
		{
			push(@info, $_);
		}
	}

	$info=join("\0", @info);

	$cdinfo{$key}=$info;
	dbmclose(%cdinfo);
	close(CD);
		
}

# parses the commandline
sub getopt
{
	local($i, $j, @other);
	for($i=0; $i<=$#_; $i++)
	{
		$_=$_[$i];
		if(/^[-]/)
		{
			for($j=1; $j<length($_[$i]); $j++)
			{
				push(@options, substr($_[$i], $j, 1));
			}
		}
		else
		{
			push(@other, $_);
		}
	}
	for(@options)
	{
		if($_ eq "e")
		{
			&edit();
			exit(0);
		}
		elsif($_ eq "E")
		{
			&eject();
			exit(0);
		}
		elsif($_ eq "d")
		{
			$debug++;
		}
		elsif($_ eq "D")
		{
			exit(&deletecd());
		}
		elsif($_ eq "l")
		{
			$play=0;
		}
		elsif($_ eq "v")
		{
			$verbose++;
		}
		elsif($_ eq "p")
		{
			$play=2;
		}
		elsif($_ eq "P")
		{
			&printdata();
			exit(1);
		}
		elsif($_ eq "q")
		{
			$verbose--;
		}
		elsif($_ eq "r")
		{
			$random=1;
		}
		elsif($_ eq "R")
		{
			$random=2;
		}
		elsif($_ eq "s")
		{
			$play=-1;
		}
		elsif($_ eq "S")
		{
			&stop();
			exit(0);
		}
		else
		{
			print "Invalid option ``$_''\n";
			&usage();
			exit(1);
		}
	}
	return(@other);
}


# this takes 4:3 and makes 4:03 out of it
sub fixtime
{
	local($time)=@_;
	local($min, $sec)=split(':', $time);
	if($sec<10)
	{
		$sec="0$sec";
	}

	return "$min:$sec";
}


# opens CD, %cdinfo, creates @nameinfo if possible, and returns an
# array of cdinfo
sub getcdinfo
{
	local(%cdinfo, $key, $tracks, $blah, $header, @a);
	local(@tmp, $tocentry, @list, @cdinfo);

	open(CD, "$cddev");

	if($verbose>0)
	{
		dbmopen(%cdinfo, "$database", undef);
	}

	$header="\0\0";
	print "Reading:  Header... " if($debug>0);
	ioctl(CD, &CDROMREADTOCHDR, $header) || die("Read TOCHDR: $!");
	@a=unpack($tochdr_t, $header);
	$tracks=$a[1]-$a[0]+1;
	print "Tracks... " if($debug>0);
	for($i=$a[0];$i<=$a[1];$i++)
	{
		$tocentry=pack($tocentry_t, $i, 0, &CDROM_MSF, 0, 0, 0, 0, 0);
		ioctl(CD, &CDROMREADTOCENTRY, $tocentry) || die("Read TOC: $!");
		@list=unpack($tocentry_t, $tocentry);
		$minutes=$list[4];
		$seconds=$list[5];

		push(@cdinfo, "$minutes:$seconds");

	}
	$tocentry=pack($tocentry_t, &CDROM_LEADOUT, 0, &CDROM_MSF, 0, 0, 0, 0);
	print "Lead-out...\n" if($debug>0);
	ioctl(CD, &CDROMREADTOCENTRY, $tocentry) || die("Read TOC: $!");
	@list=unpack($tocentry_t, $tocentry);
	push(@cdinfo, "$list[4]:$list[5]");

	for(@cdinfo)
	{
		($min, $sec)=split(':');
		$sec=($min*60)+$sec;
		push(@tmp, $sec);
	}
	$key=join('\0', @tmp);

	@nameinfo=split("\0", $cdinfo{$key}) if(defined($cdinfo{$key}));

	if($verbose>0)
	{
		dbmclose(%cdinfo);
	}

	return(@cdinfo);
}

# This finds the status of the device and returns an array that has
# elements for status, and a few other bits of information about the
# thing
sub getstatus
{
	local(@subchannel, $subchannel, @rval);
	$subchannel=pack($subch_t, &CDROM_MSF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
	print "Reading Subchannel stuff\n" if($debug>1);
	ioctl(CD, &CDROMSUBCHNL, $subchannel) || die("Read Subchannel: $!");
	@subchannel=unpack($subch_t, $subchannel);
	print "Status: $status[$subchannel[1]] " if($debug>1);
	if($subchannel[1] == &CDROM_AUDIO_PLAY)
	{
		print "$subchannel[3] " if($debug>1);
		print &fixtime("$subchannel[12]:$subchannel[13]"), "\n" if($debug>1);
	}
	else
	{
		print "\n" if($debug>1);
	}
	# status
	push(@rval, $subchannel[1]);
	# if playing, what track
	push(@rval, $subchannel[3]);
	# if playing, how many minutes into the track
	push(@rval, $subchannel[12]);
	# ... how many seconds
	push(@rval, $subchannel[13]);

	return(@rval);
}

# gives the length of the track passed to it
sub tracklen
{
	local($track)=@_;
	return &fixtime(&lengthof($cdinfo[$track-1], $cdinfo[$track]));
}

# this subtracts mm1:ss1 - mm2:ss2 and returns mm:ss
sub lengthof
{
	local($start1, $start2)=@_;
	local($min1, $sec1, $min2, $sec2, $secdiff, $mindiff);

	($min1, $sec1)=split(':', $start1);
	($min2, $sec2)=split(':', $start2);

	$sec1=(60*$min1)+$sec1;
	$sec2=(60*$min2)+$sec2;

	$secdiff=$sec2-$sec1;
	$mindiff=int($secdiff/60);
	$secdiff=$secdiff%60;

	return "$mindiff:$secdiff";
}

# this routine will play a track with optional arguments.
# if there are no arguments, it plays beginning to end, if there
# is one argument, it plays from that numbered track to the end,
# if there are two arguments, it plays from the first numbered
# track to the second numbered track.
sub play
{
	local($start, $stop)=@_;
	local($blah, @len);

	if($#_<1)
	{
		if($#_<0)
		{
			$start=1;
		}
		$stop=$tracks;
	}

	if( ($start>$tracks)||($start<1))
	{
		print("Invalid track Number\n") if($verbose>0);
		exit(1);
	}

	if($verbose>0)
	{
		@len=split(':', &tracklen($start));
		print "Playing $start:\t$len[0]:$len[1]";
		print "\t$nameinfo[$start+1]" if(defined($nameinfo[$start+1]));
		print "\n";
	}

	$blah=pack($ti_t, $start, 1, $stop, 1);
	ioctl(CD, &CDROMPLAYTRKIND, $blah) || die("Play track: $!");
}

# Stops the cd player
sub stop
{
	local($nothing);
	open(CD, "$cddev");
	print "Stopping\n" if($verbose>0);
	ioctl(CD, &CDROMSTOP, $nothing) || die("CD Stop: $!");
}

# ejects the CD
sub eject
{
	local($nothing);
	open(CD, "$cddev");
	print "Ejecting\n" if($verbose>0);
	ioctl(CD, &CDROMEJECT, $nothing) || die("CD Eject: $!");
}

# this creates a random array of the track numbers
sub shuffle
{
	local($something, $blah, $tracks)=(0, 0, @_);
	local(@stack);

	for(1..$tracks)
	{
		push(@stack, $_);
	}

	while($#stack>=0)
	{
		$blah=int( rand()*$#stack );

		($something)=splice(@stack, $blah, 1);
		push(@shuffled, $something);
	}
	return(@shuffled);
}

# This plays the tracks specified on the commandline
sub playstraight
{
	&play(@_);
}

# This plays one random track on a CD
sub playonerandom
{
	local($blah)=int((rand() * $tracks)+1);
	play($blah, $blah);
}

sub waitforcd
{
	local($start)=@_;
	local(@subch);

	$start=1 if(!defined($start));

	@subch=&getstatus();
	return if($subch[0] != &CDROM_AUDIO_PLAY);
	sleep $start-5;

	while($subch[0] == &CDROM_AUDIO_PLAY)
	{
		@subch=&getstatus();
		sleep 1;
	}
	
}

# This plays a CD randomly
sub playshuffle
{
	local(@shuffled)=shuffle($tracks);
	local(@subch, @len, $sec);

	if($debug>0)
	{

		print "Shuffle sequence:\n";
		for(@shuffled)
		{
			print "$_ ";
		}
		print "\n";
	}

	for(@shuffled)
	{
		@len=split(':', &tracklen($_));
		$sec=($len[0]*60)+$len[1];
		&play($_, $_);
		&waitforcd($sec);
	}
}

# BEGINNING

srand(time());

@ARGV=&getopt(@ARGV);

@cdinfo=&getcdinfo();
$tracks=$#cdinfo;
@subch=&getstatus();

$verbose++ if($play==0);

print "cdthing version $id[2] by Dustin Sallings\n" if($verbose>0);

if($verbose>1)
{
	print "CD name:\t$nameinfo[1]\n" if(defined($nameinfo[1]));
	print "Artist: \t$nameinfo[0]\n\n" if(defined($nameinfo[0]));
	for ($blah=1; $blah<=$tracks; $blah++)
	{
		print "$blah:\t", &tracklen($blah);
		print "\t$nameinfo[$blah+1]" if(defined($nameinfo[$blah+1]));
		print "\n";
	}
}

print "Found $tracks tracks\n" if($verbose>1);

if($play==1)
{
	if($random==0)
	{
		&play(@ARGV);
	}
	else
	{
		if($random==1)
		{
			&playonerandom()
		}
		else
		{
			&playshuffle();
		}
	}
}
# Play program
elsif($play==2)
{
	if($debug>0)
	{
		for(@ARGV)
		{
			print "Going to play $_\n";
		}
	}
	for(@ARGV)
	{
		@len=split(':', &tracklen($_));
		$sec=($len[0]*60)+$len[1];
		&play($_, $_);
		&waitforcd($sec);
	}
}
elsif($play==-1)
{
	if(defined($nameinfo[0]) && defined($nameinfo[1]))
	{
		print "CDname: $nameinfo[1]\n";
		print "Artist:  $nameinfo[0]\n";
	}
	else
	{
		print "CDname: unknown\nArtist: unknown\n";
	}
	print "Status:\t", $status[$subch[0]];
	if($subch[0] == &CDROM_AUDIO_PLAY)
	{
		print " track $subch[1] ";
		print "(", $nameinfo[$subch[1]+1], ") "
			if(defined($nameinfo[$subch[1]+1]));
		print "at ", &fixtime("$subch[2]:$subch[3]");
	}
	print "\n";
}

close(CD);
