#!/usr/bin/perl -w # mp3index: Program for generating a listing of MP3 files. # This program is licensed under Version 2 of the GPL. # See the file COPYING for more information. # # Run the program with no arguments for more information, # including usage licensing. # # Feb 6 1999 - Version 0.3: Intial release. # Feb 11 1999 - Version 0.5: Added support for ID3. The MP3 module is now # used instead of running the mp3info program. # Feb 22 1999 - Version 0.7: Too many changes to list here - see HISTORY # Apr 07 1999 - Version 1.0: Removed ? option specifier for backwards # compatibilty. # Oct 20 1999 - Version 1.1: Better cross-platform support and support for # --keeppath, --noparse, and --alternatepat # options, VBR support # Jun 15 2000 - Version 1.2: No changes in mp3index # Jan 16 2002 - Version 1.4: M3U support $version = "1.5"; use MPEG::MP3Info; use Getopt::Long; use Text::Wrap qw(wrap); use File::Basename; if($MPEG::MP3Info::VERSION > 0.71) { $vbrsupport = 1 } else { $vbrsupport = 0; } sub describe { open FMTFILE; while() { s/^[ \t]+//; next if /^$/; last unless /^#/; s/^#[ \t]+//; /([^ \t]+)/; $descriptor = $1; if(lc($descriptor) eq "formatfile") { /[^ \t]+[ \t]+(.+)/; $formatfile = $1; } elsif(lc($descriptor) eq "formats") { /[^ \t]+[ \t]+(\d+)/; $numformats = $1; } elsif(lc($descriptor) eq "format") { /[^ \t]+[ \t]+(\d+)[ \t]+([^ \t]+)[ \t]+(.+)/; $formatnum = $1; $descriptions[$formatnum] = $3 if lc($2) eq "desc"; $options[$formatnum] = [split(/\^/, $3)] if lc($2) eq "options"; } } $numformats ||= $#descriptions; print "Format file overview: $formatfile\n"; print "Number of formats: $numformats\n"; print "\nDescriptions:\n\n"; $i = 1; foreach $desc(@descriptions) { next unless $desc; print "Output file $i:\n"; print wrap("\t", "\t", ($desc)); print "\n\n"; $j = 1; foreach $s(@{$options[$i++]}) { print "\tOption " . $j++ . ":\n"; print wrap("\t\t", "\t\t", ($s)); print "\n"; } print "\n"; } exit 0; } sub prettysize { my ($size, $longname) = (shift, shift); if($longname) { $kilos = " kilobytes"; $megas = " megabytes"; } else { $kilos = "k"; $megas = "M"; } if($size >= 1024 and $size < (1024*1024)) { #kilobytes return (sprintf "%0.2f", ($size / 1024)) . $kilos; } elsif($size >= (1024*1024)) { #megabytes return (sprintf "%0.2f", ($size / (1024*1024))) . $megas; } else { #bytes return "$size bytes"; } } sub writeout { my $curr = shift; my $whichoutput = $curr - 1; $curr = "" if $curr == 1; my $aref; my $old = select OUTFILE; (&{"outhead$curr"} or die "Format file didn't define outhead$curr!\n") unless $bodyonly; foreach $mp3(@mp3) { $artist = $artist{$mp3}; $title = $title{$mp3}; $ftpurl = $ftpurl{$mp3} if $ftpurl{$mp3}; $httpurl = $httpurl{$mp3} if $httpurl{$mp3}; $playurl = $playurl{$mp3} if $playurl{$mp3}; $bitrate = $bitrate{$mp3}; $length = $length{$mp3}; $size = $size{$mp3}; $album = $album{$mp3}; $year = $year{$mp3}; $comment = $comment{$mp3}; $vbr = $vbr{$mp3} || 0; $genre = $genre{$mp3}; $prettysize = prettysize($size); $prettylongsize = prettysize($size, 1); $foo = $prettysize; $foo = $prettylongsize; #squash warnings $aref = $data{$mp3}; #ref to array @data = @$aref if defined($aref); @options = split(/\^/, $OPTIONS[$whichoutput]) if defined($OPTIONS[$whichoutput]); if ($passlist) { push @list, \[ vbr => $vbr, filename => $mp3, artist => $artist, title => $title, ftpurl => $ftpurl, httpurl => $httpurl, playurl => $playurl, bitrate => $bitrate, "length" => $length, size => $size, album => $album, year => $year, comment => $comment, genre => $genre, "prettysize" => $prettysize, prettylongsize => $prettylongsize, data => \[ @data ], ]; @list = @list; #squelch -w } else { &{"outbody$curr"} or die "Format file didn't define outbody$curr!\n"; } } &{"outbody$curr"} if $passlist; (&{"outfoot$curr"} or die "Format file didn't define outfoot$curr!\n") unless $bodyonly; close OUTFILE unless $noopen; select $old; } sub dispver { print "mp3index: Generates custom indexes of MPEG audio files.\nVersion: $version\n"; print "Use mp3index --help for more information.\n"; exit 0; } sub help { print < \&dispver, "ftpurl|c=s" => \$ftpurl, "httpurl|h=s" => \$httpurl, "playurl|p=s" => \$playurl, "datfile|d=s" => \$DATFILE, "output|o=s\@" => \@OUTFILE, "options|O=s\@" => \@OPTIONS, "format|layout|l=s" => \$FMTFILE, "no-winamp|n" => \$standard, "verbose|v" => \$verbose, "aggressive|a" => \$MPEG::MP3Info::try_harder, "describe|D" => \$describe, "playall_path|P=s" => \$playall_path, "playall_url|U=s" => \$playall_url, "keeppath|k" => \$keeppath, "noparse|N" => \$noparse, "alternatepat|A=s" => \$altpat, "help|usage" => \&help, "m3u|M=s\@" => \@m3u_files, ); #"format|layout|l=s@" => \@FMTFILE, if(!$errors) { warn "Try mp3index --help if you need help.\n"; exit 1; } $FMTFILE ||= "-"; &describe if $describe; if(!$ARGV[0] and !@m3u_files) { print "You must pass at least one MPEG audio file as a command-line argument!\n"; print "(Or use the --m3u option.)\n"; exit 1; } &use_winamp_genres unless $standard; $ftpurl ||= ""; $httpurl ||= ""; $ftpurl =~ s!^(.+)/$!$1!; #strip trailing slash $httpurl =~ s!^(.+)/$!$1!; if($httpurl eq ".") { $httpurl = ""; } if($httpurl and $httpurl ne "") { $httpurl .= "/"; } if($ftpurl eq ".") { $ftpurl = ""; } if($ftpurl and $ftpurl ne "") { $ftpurl .= "/"; } if(@m3u_files) { warn "Processing M3U files...\n" if $verbose; foreach my $m3u(@m3u_files) { open(M3U, $m3u) or die "Couldn't open M3U file $m3u: $!\n"; while() { chomp; push @ARGV, $_; } close M3U; } } warn "Processing MPEG audio files...\n" if $verbose; $totalsize = 0; $totalmins = 0; $totalhours = 0; $totalsecs = 0; foreach $mp3(@ARGV) { die "Couldn't find $mp3!\n" unless -f $mp3; $fullname = $mp3; $mp3 = basename($mp3) unless $keeppath; push @mp3, $mp3; $totalsize += ($size{$mp3} = (stat $fullname)[7]); $altpat ||= '(.+?) - (.+)\..+'; ($mp3 =~ /$altpat/ or $mp3 =~ /^()(.+)\..+/) unless $noparse; $artist{$mp3} = $1; $title{$mp3} = $2; $ftpurl{$mp3} = $ftpurl . $mp3 if $ftpurl; $tempmp3 = $mp3; $tempmp3 =~ s/ /%20/g; $tempmp3 =~ s/&/%26/g; $tempmp3 =~ s/'/%27/g; $tempmp3 =~ s/\(/%28/g; $tempmp3 =~ s/\)/%29/g; $tempmp3 =~ s/#/%23/g; $tempmp3 =~ s/\[/%5b/g; $tempmp3 =~ s/]/%5d/g; $tempmp3 =~ s/-/%2d/g; $httpurl{$mp3} = $httpurl . $tempmp3 if $httpurl; $playurl{$mp3} = "$playurl?url=$httpurl{$mp3}" if $playurl and $httpurl{$mp3}; $mp3info = get_mp3info($fullname); $id3tag = get_mp3tag($fullname); $bitrate{$mp3} = $mp3info->{BITRATE}; $vbr{$mp3} = $foo->{VBR} if $vbrsupport and $foo->{VBR}; $hh = int $mp3info->{MM} / 60; $mm = $mp3info->{MM} % 60; $length = sprintf "%2.2d:%2.2d:%2.2d\n", $hh, $mm, $mp3info->{SS}; $title{$mp3} = $id3tag->{TITLE} if $id3tag->{TITLE}; $artist{$mp3} = $id3tag->{ARTIST} if $id3tag->{ARTIST}; $album{$mp3} = $id3tag->{ALBUM} if $id3tag->{ALBUM}; $year{$mp3} = $id3tag->{YEAR} if $id3tag->{YEAR}; $comment{$mp3} = $id3tag->{COMMENT} if $id3tag->{COMMENT}; $genre{$mp3} = $id3tag->{COMMENT} if $id3tag->{COMMENT}; ($h, $m, $s) = split(":", $length); $totalsecs += $s; if($totalsecs >= 60) { $totalsecs -= 60; $totalmins++; } $totalmins += $m; if($totalmins >= 60) { $totalmins -= 60; $totalhours++; } $totalhours += $h; $length{$mp3} = $length; } if($playall_url) { open PLAYOUT, ">$playall_path" or die "Could not open $playall_path: $!\n"; print PLAYOUT "#!/usr/bin/perl\n\n"; print PLAYOUT "use CGI qw(:standard);\n"; print PLAYOUT 'print "Content-Type: audio/mpeg-url\n\n";' . "\n"; print PLAYOUT "print <)) { chomp; ($mp3, @data) = split(/\^/); $data{$mp3} = [@data]; } close DATFILE; warn "Processing format files...\n" if $verbose; #$fmtnum = 0; $temp = ""; #foreach $FMTFILE(@FMTFILE) { open FMTFILE or die "Couldn't open format file: $!\n"; while() { $temp .= $_; } close FMTFILE; #} #warn "Renumbering subroutines...\n" if $verbose; # #sub getfmt { # my $f = shift; $fmtnum++; # return $fmtnum unless $fmtnum == 1; return ""; #} # #while ($temp =~ s!\G.+?sub outhead([\d]*)?!sub outhead@{[&getfmt(1)]}!ig) { # $oldnum = $1; # warn "Renumbering $oldnum to $fmtnum\n" if $verbose; # $temp =~ s!sub outbody$oldnum!sub outhead@{[&getfmt(0)]}!i; # $temp =~ s!sub outfoot$oldnum!sub outhead@{[&getfmt(0)]}!i; #} $foo = $^W; $^W = 0; #See the sample format file for info on these variables $noopen = 0; $bodyonly = 0; $passlist = 0; $^W = $foo; warn "Evalling format file...\n" if $verbose; @options = split(/\^/, $OPTIONS[0]) if defined($OPTIONS[0]); eval $temp; die "Eval error: $@\n" if $@; if(!$OUTFILE[0]) { warn "Processing outfile...\n" if $verbose; *OUTFILE = *STDOUT; #Alias OUTFILE to STDOUT writeout(1); } else { $curr = 1; foreach $OUTFILE(@OUTFILE) { warn "Processing outfile $curr.\n" if $verbose; (open OUTFILE, ">$OUTFILE" or die "Couldn't open output file: $!\n") unless $noopen; writeout($curr); $curr++; } } warn "Done.\n" if $verbose;