# # CDDB - Read the CDDB entry for an audio CD in your drive # # This module/script gets the CDDB info for an audio cd. You need # LINUX, a cdrom drive and an active internet connection in order # to do that. # # (c) 2000 Armin Obersteiner # changes to CDDB_get2 Copyright 2002-2004 Russ Burdick, # grub@extrapolation.net # # LICENSE # # This library is released under the same conditions as Perl, that # is, either of the following: # # a) the GNU General Public License as published by the Free # Software Foundation; either version 1, or (at your option) any # later version. # # b) the Artistic License. # package CDDB_get2; use strict; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $debug); require Exporter; @ISA = qw(Exporter AutoLoader); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. @EXPORT_OK = qw( get_cddb get_discids ); $VERSION = '1.66'; use Fcntl; use IO::Socket; #$debug=1; # setup for linux, solaris x86, solaris spark # you freebsd guys give me input print STDERR "cddb: checking for os ... " if $debug; my $os=`uname -s`; my $machine=`uname -m`; chomp $os; chomp $machine; print STDERR "$os ($machine) " if $debug; # cdrom IOCTL magic (from c headers) # linux x86 is default # /usr/include/linux/cdrom.h my $CDROMREADTOCHDR=0x5305; my $CDROMREADTOCENTRY=0x5306; my $CDROM_MSF=0x02; # default config my $CDDB_HOST = "freedb.freedb.org"; my $CDDB_PORT = 888; my $CDDB_MODE = "cddb"; my $CD_DEVICE = "/dev/cdrom"; my $HELLO_ID = "root nowhere.com fastrip 0.77"; # endian check my $BIG_ENDIAN = unpack("h*", pack("s", 1)) =~ /01/; if($BIG_ENDIAN) { print STDERR "[big endian]\n" if $debug; } else { print STDERR "[little endian]\n" if $debug; } if($os eq "SunOS") { # /usr/include/sys/cdio.h $CDROMREADTOCHDR=0x49b; # 1179 $CDROMREADTOCENTRY=0x49c; # 1180 if(-e "/vol/dev/aliases/cdrom0") { $CD_DEVICE="/vol/dev/aliases/cdrom0"; } else { if($machine =~ /^sun/) { # on sparc and old suns $CD_DEVICE="/dev/rdsk/c0t6d0s0"; } else { # on intel $CD_DEVICE="/dev/rdsk/c1t0d0p0"; } } } elsif($os =~ /BSD/i) { # works for netbsd, infos for other bsds welcome # /usr/include/sys/cdio.h $CDROMREADTOCHDR=0x40046304; $CDROMREADTOCENTRY=0xc0086305; $CD_DEVICE="/dev/cd0a"; if($os eq "OpenBSD") { $CD_DEVICE="/dev/cd0c"; } } sub read_toc { my $device=shift; my $tochdr=""; sysopen (CD,$device, O_RDONLY | O_NONBLOCK) or die "cannot open cdrom [$!]"; ioctl(CD, $CDROMREADTOCHDR, $tochdr) or die "cannot read toc [$!]"; my ($start,$end); if($os =~ /BSD/) { ($start,$end)=unpack "CC",(substr $tochdr,2,2); } else { ($start,$end)=unpack "CC",$tochdr; } my @tracks=(); for (my $i=$start; $i<=$end;$i++) { push @tracks,$i; } push @tracks,0xAA; my @r=(); my $tocentry; my $toc=""; my $size=0; for(@tracks) { $toc.=" "; $size+=8; } if($os =~ /BSD/) { my $size_hi=int($size / 256); my $size_lo=$size & 255; if($BIG_ENDIAN) { $tocentry=pack "CCCCP8l", $CDROM_MSF,0,$size_hi,$size_lo,$toc; } else { $tocentry=pack "CCCCP8l", $CDROM_MSF,0,$size_lo,$size_hi,$toc; } ioctl(CD, $CDROMREADTOCENTRY, $tocentry) or die "cannot read track info [$!]"; } my $count=0; foreach my $i (@tracks) { my ($min,$sec,$frame); unless($os =~ /BSD/) { $tocentry=pack "CCC", $i,0,$CDROM_MSF; ioctl(CD, $CDROMREADTOCENTRY, $tocentry) or die "cannot read track $i info [$!]"; ($min,$sec,$frame)=unpack "CCCC", substr($tocentry,4,4); } else { ($min,$sec,$frame)=unpack "CCC", substr($toc,$count+5,3); } $count+=8; my %cdtoc=(); $cdtoc{min}=$min; $cdtoc{sec}=$sec; $cdtoc{frame}=$frame; $cdtoc{frames}=int($frame+$sec*75+$min*60*75); push @r,\%cdtoc; } close(CD); return @r; } sub cddb_sum { my $n=shift; my $ret=0; while ($n > 0) { $ret += ($n % 10); $n = int $n / 10; } return $ret; } sub cddb_discid { my $total=shift; my $toc=shift; my $i=0; my $t=0; my $n=0; while ($i < $total) { $n = $n + cddb_sum(($toc->[$i]->{min} * 60) + $toc->[$i]->{sec}); $i++; } $t = (($toc->[$total]->{min} * 60) + $toc->[$total]->{sec}) - (($toc->[0]->{min} * 60) + $toc->[0]->{sec}); return (($n % 0xff) << 24 | $t << 8 | $total); } sub get_discids { my $cd=shift; $CD_DEVICE = $cd if (defined($cd)); my @toc=read_toc($CD_DEVICE); my $total=$#toc; my $id=cddb_discid($total,\@toc); return [$id,$total,\@toc]; } sub get_cddb { my $config=shift; my $diskid=shift; my $id; my $toc; my $total; my $input=$config->{input}; $CDDB_HOST = $config->{CDDB_HOST} if (defined($config->{CDDB_HOST})); $CDDB_PORT = $config->{CDDB_PORT} if (defined($config->{CDDB_PORT})); $CDDB_MODE = $config->{CDDB_MODE} if (defined($config->{CDDB_MODE})); $CD_DEVICE = $config->{CD_DEVICE} if (defined($config->{CD_DEVICE})); $HELLO_ID = $config->{HELLO_ID} if (defined($config->{HELLO_ID})); my $HTTP_PROXY = $config->{HTTP_PROXY} if (defined($config->{HTTP_PROXY})); if(defined($diskid)) { $id=$diskid->[0]; $total=$diskid->[1]; $toc=$diskid->[2]; } else { my $diskid=get_discids($CD_DEVICE); $id=$diskid->[0]; $total=$diskid->[1]; $toc=$diskid->[2]; } my @list=(); my $return; my $socket; my $id2 = sprintf "%08x", $id; my $query = "cddb query $id2 $total"; for (my $i=0; $i<$total ;$i++) { $query.=" $toc->[$i]->{frames}"; } $query.=" ". int(($toc->[$total]->{frames}-$toc->[0]->{frames})/75); if ($CDDB_MODE eq "cddb") { print STDERR "cddb: connecting to $CDDB_HOST:$CDDB_PORT\n" if $debug; $socket=IO::Socket::INET->new(PeerAddr=>$CDDB_HOST, PeerPort=>$CDDB_PORT, Proto=>"tcp",Type=>SOCK_STREAM) or die "cannot connect to cddb db: $CDDB_HOST:$CDDB_PORT [$!]"; $return=<$socket>; unless ($return =~ /^2\d\d\s+/) { die "not welcome at cddb db"; } print $socket "cddb hello $HELLO_ID\n"; $return=<$socket>; unless ($return =~ /^2\d\d\s+/) { die "handshake error at cddb db: $CDDB_HOST:$CDDB_PORT"; } print STDERR "cddb: sending: $query\n" if $debug; print $socket "$query\n"; $return=<$socket>; chomp $return; print STDERR "cddb: result: $return\n" if $debug; } elsif ($CDDB_MODE eq "http") { my $query2=$query; $query2 =~ s/ /+/g; my $id=$HELLO_ID; $id =~ s/ /+/g; my $url = "/~cddb/cddb.cgi?cmd=$query2&hello=$id&proto=5"; my $host=$CDDB_HOST; my $port=80; if($HTTP_PROXY) { if($HTTP_PROXY =~ /^(http:\/\/|)(.+?):(\d+)$/) { $host=$2; $port=$3; $url="http://$CDDB_HOST".$url." HTTP/1.0\n"; } } print STDERR "cddb: connecting to $host:$port\n" if $debug; $socket=IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>"tcp",Type=>SOCK_STREAM) or die "cannot connect to cddb db: $host:$port [$!]"; print STDERR "cddb: http send: GET $url\n" if $debug; print $socket "GET $url\n"; if($HTTP_PROXY) { while(<$socket> =~ /^\S+/){}; } $return=<$socket>; chomp $return; print STDERR "cddb: http result: $return\n" if $debug; } else { die "unkown mode: $CDDB_MODE for querying cddb"; } my ($err) = $return =~ /^(\d\d\d)\s+/; unless ($err =~ /^2/) { die "query error at cddb db: $CDDB_HOST:$CDDB_PORT"; } if($err==202) { return undef; } elsif(($err==211) || ($err==210)) { while(<$socket>) { last if(/^\./); push @list,$_; } } elsif($err==200) { } else { die "cddb: unknown: $return"; } if (@list) { my $index; if($input==1) { print "This CD could be:\n\n"; my $i=1; for(@list) { my ($tit) = $_ =~ /^\S+\s+\S+\s+(.*)/; print "$i: $tit\n"; $i++ } print "\n0: none of the above\n\nChoose: "; my $n=; $index=int($n); } else { $index=1; } if ($index == 0) { return undef; } else { $return="200 ".$list[$index-1]; } } #200 misc 0a01e802 Meredith Brooks / Bitch Single my ($cat,$at); ($cat,$id,$at) = $return =~ /^\d\d\d\s+(\S+)\s+(\S+)\s+(.*)/; my $artist; my $title; if($at =~ / \/ /) { ($artist,$title)= $at =~ /(.*?)\s* \/ \s*(.*)/; } elsif($at =~ /\//) { ($artist,$title)= $at =~ /(.*?)\s*\/\s*(.*)/; } else { $artist=$at; $title=$at; } my %cd=(); $cd{artist}=$artist; chop $title; $cd{title}=$title; $cd{cat}=$cat; $cd{id}=$id; my @lines; $query="cddb read $cat $id"; if ($CDDB_MODE eq "cddb") { print STDERR "cddb: getting: $query\n" if $debug; print $socket "$query\n"; while(<$socket>) { last if(/^\./); push @lines,$_; } print $socket "quit\n"; close $socket; } elsif ($CDDB_MODE eq "http") { close $socket; my $query2=$query; $query2 =~ s/ /+/g; my $id=$HELLO_ID; $id =~ s/ /+/g; my $url = "/~cddb/cddb.cgi?cmd=$query2&hello=$id&proto=5"; my $host=$CDDB_HOST; my $port=80; if($HTTP_PROXY) { if($HTTP_PROXY =~ /^(http:\/\/|)(.+?):(\d+)$/) { $host=$2; $port=$3; $url="http://$CDDB_HOST".$url." HTTP/1.0\n"; } } print STDERR "cddb: connecting to $host:$port\n" if $debug; $socket=IO::Socket::INET->new(PeerAddr=>$host, PeerPort=>$port, Proto=>"tcp",Type=>SOCK_STREAM) or die "cannot connect to cddb db: $host:$port [$!]"; print STDERR "cddb: http send: GET $url\n" if $debug; print $socket "GET $url\n"; if($HTTP_PROXY) { while(<$socket> =~ /^\S+/){}; } while(<$socket>) { last if(/^\./); push @lines,$_; } close $socket; } else { die "unkown mode: $CDDB_MODE for querying cddb"; } for(@lines) { last if(/^\./); next if(/^\d\d\d/); push @{$cd{raw}},$_; #TTITLE0=Bitch (Edit) if(/^TTITLE(\d+)\=\s*(.*)/) { my $t= $2; chop $t; $cd{frames}[$1]=$toc->[$1]->{frames}; unless (defined $cd{track}[$1]) { $cd{track}[$1]=$t; } else { $cd{track}[$1]=$cd{track}[$1].$t; } $cd{track}[$1] =~ s/\\n//g; } elsif (/^DYEAR\=\s*(.*)/) { my $y = $1; chop $y; unless (defined $cd{year}) { $cd{year}=$y; } else { $cd{year}=$cd{year}.$y; } print STDERR "cddb: DYEAR=$y\n" if $debug; } } $cd{tno}=$#{$cd{track}}+1; $cd{frames}[$cd{tno}]=$toc->[$cd{tno}]->{frames}; return \%cd; } 1; __END__ # Below is the stub of documentation for your module. You better edit it! =head1 NAME CDDB - Read the CDDB entry for an audio CD in your drive =head1 SYNOPSIS use CDDB_get qw( get_cddb ); my %config; # following variables just need to be declared if different from defaults $config{CDDB_HOST}="freedb.freedb.org"; # set cddb host $config{CDDB_PORT}=8880; # set cddb port $config{CDDB_MODE}="cddb"; # set cddb mode: cddb or http $config{CD_DEVICE}="/dev/cdrom"; # set cd device # user interaction welcome? $config{input}=1; # 1: ask user if more than one possibility # 0: no user interaction # get it on my $cd = get_cddb(\%config); unless(defined $cd->{title}) { die "no cddb entry found"; } # do somthing with the results print "artist: $cd->{artist}\n"; print "title: $cd->{title}\n"; print "category: $cd->{cat}\n"; print "cddbid: $cd->{id}\n"; print "trackno: $cd->{tno}\n"; my $n=1; foreach my $i ( @{$cd->{track}} ) { print "track $n: $i\n"; $n++; } =head1 DESCRIPTION This module/script gets the CDDB info for an audio cd. You need LINUX, SUNOS or NETBSD, a cdrom drive and an active internet connection in order to do that. =head1 INSTALLATION Run "perl Makefile.pl" as usual. ("make", "make install" next) =head1 LICENSE & DISCLAIMER This library is released under the same conditions as Perl, that is, either of the following: a) the GNU General Public License Version 2 as published by the Free Software Foundation, b) the Artistic License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the Artistic License for more details. You should have received a copy of the Artistic License with this Kit, in the file named "Artistic". If not, I'll be glad to provide one. You should also have received a copy of the GNU General Public License along with this program, in the file names "Copying"; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. If you use this library in a commercial enterprise, you are invited, but not required, to pay what you feel is a reasonable fee to the author, who can be contacted at armin@xos.net =head1 AUTHOR & COPYRIGHT (c) 2001 Armin Obersteiner =head1 SEE ALSO perl(1), Linux: , Solaris: . =cut