#!/usr/bin/perl # # $Id: mp3shell,v 0.24 2000/02/21 20:20:41 toffi Exp $ # # # mp3shell Shell like interface for managing and playing mp3s # # Author: Christoph Hellwig # # # $Log: mp3shell,v $ # Revision 0.24 2000/02/21 20:20:41 toffi # - fixed small bug in ' Playing MPEG stream' line # # Revision 0.23 2000/02/20 20:10:06 toffi # - fixed rcfile parsing # # Revision 0.22 2000/02/20 19:37:31 toffi # - added clear command # - wrote manpage # - added Makefile and README # # Revision 0.21 2000/02/20 16:58:51 toffi # - readded support for skipping one song # - added sighandlers # # Revision 0.2 2000/02/20 16:16:16 toffi # - added support for rcfile # - implemented statusbar # # Revision 0.13 2000/02/14 21:24:20 toffi # - even more bugfixes # # Revision 0.12 2000/02/14 17:29:25 toffi # - some misc bugfixes # - added some new useless features :) # # Revision 0.11 2000/02/13 14:07:53 toffi # - first simple patch # use DB_File; use Fcntl; use Getopt::Std; use Term::ReadKey; use Term::ReadLine; require "find.pl"; getopts('c:'); # config $VERSION = "v0.12"; $ext = $$; $fifo = "/tmp/fifo.$ext"; $maxlen = 30; $prompt = "mp3 shell> "; $mpg123opts = ""; $cf_file = "$ENV{'HOME'}/.mp3shellrc"; $ba_file = "$ENV{'HOME'}/.mp3shell.by_artist"; $bn_file = "$ENV{'HOME'}/.mp3shell.by_name"; #$SIG{__DIE__} = $SIG{INT} = \&quit; #if ( -f $cf_file ) { # open (CFG, "<$cf_file"); # while() { # eval $_; # } # close CFG; #} $term = new Term::ReadLine 'Perl mp3 shell'; $OUT = $term->OUT || STDOUT; #if($opt_c) { # unlink $ba_file; # unlink $bn_file; #} $DB_BTREE->{'flags'} = R_DUP; $ba_h = tie %playlist_ba, "DB_File", $ba_file, O_RDWR|O_CREAT, 0640, $DB_BTREE; $bn_h = tie %playlist_bn, "DB_File", $bn_file, O_RDWR|O_CREAT, 0640, $DB_BTREE; if($opt_c) { print $OUT "Recreating mp3 database using $opt_c as base directory\n" &find($opt_c); } if (system "mkfifo $fifo || mknod -p $fifo") { die "mk{nod,fifo} $fifo failed"; } sub wanted { s/^(.*)\.mp3$/\1/ or return; open (MP3, "<$name"); seek MP3, -128, 2; read MP3, $tag, 3; if($tag eq "TAG") { my ($title, $artist); read MP3, $title, 30; $title =~ s/\s*$//; $playlist_bn{$title} = $name; read MP3, $artist, 30; $artist =~ s/\s*$//; $playlist_ba{$artist} = $name; } elsif(/(.+?)\s-\s(.*)/) { $playlist_ba{$1} = $name; $playlist_bn{$2} = $name; } else { $playlist_ba{'Unknown'} = $name; $playlist_bn{$_} = $name; } close (MP3); } sub help { print $OUT "mp3 engine $VERSION\n\n"; print $OUT "The following commands are aviable:\n"; print $OUT "\n\tlist /expr/\n"; print $OUT "\t\tlist all mp3 files matching expr\n"; print $OUT "\n\tplay /expr/\n"; print $OUT "\t\tplay all mp3 files matching expr\n"; print $OUT "\n\tquit\n"; print $OUT "\t\tquit mp3 engine\n"; print $OUT "\n\thelp\n"; print $OUT "\t\tgive this help screen\n"; } sub id3 { my ($file, $what) = @_; my ($tag, %id3); open (MP3, "<$file"); seek MP3, -128, 2; read MP3, $tag, 3; if($tag eq "TAG") { read MP3, $id3{'title'}, 30; read MP3, $id3{'artist'}, 30; read MP3, $id3{'album'}, 30; read MP3, $id3{'year'}, 4; read MP3, $id3{'comment'}, 30; } else { $id3{'title'} = $what." (???)"; } close (MP3); return %id3; } sub step { my ($what, $exec) = @_; my ($i, $k, $v, $s); $what =~ s/^\/([^\/]+)\/$/\1/; $s = $bn_h->seq ($k, $v, R_FIRST); until ($s) { &$exec("Title", $k ,$v, ++$i) if ($k =~ /$what/i); $s = $bn_h->seq ($k, $v, R_NEXT); } $s = $ba_h->seq ($k, $v, R_FIRST); until ($s) { &$exec("Artist", $k ,$v, ++$i) if ($k =~ /$what/i); $s = $ba_h->seq ($k, $v, R_NEXT); } } $list = sub { my ($string, $what, $where, $count) = @_; print $OUT "\t\t$string: $what\n"; print $OUT "\t\tFile: $where\n\n"; }; $play = sub { my ($string, $what, $where, $count) = @_; my (%id, $percent, $barlen, $pid, $name, $sh, $i, $x, $y); %id = id3($where, $what); $what =~ s/^\s//; $what =~ s/\s$//; $name = $where; $name =~ s/.*\/(.*)/\1/; print $OUT "\tPlaying MPEG stream from $name ...\n"; print $OUT "\t\tTitle:\t$id{'title'}\n"; print $OUT "\t\tArtist:\t$id{'artist'}\n\n"; local $/ = " "; my ($sh) = "exec mpg123 -v $MPG123OPTS \"$where\" > $fifo 2>&1"; my $mypid = fork(); if ($mypid == 0) { system($sh); exit 0; } open (FIFO, "<$fifo"); ReadMode 4; # Turn off controls keys while () { if ($key = ReadKey(-1)) { last if ($key eq "n"); &quit if ($key eq "q"); } # Frame# 129 [19515], Time: 00:03.36 [08:29.77], next unless (/Frame.*Time:\s(\S\S):(\S\S)\.(\S\S)\s\[(\S\S):(\S\S)\.(\S\S)\].*/); unless (defined $i) { $y = $4.$5.$6; $i = 1; } $x = $1.$2.$3; $percent = 100.0 * $x / $y ; $barlen = $percent / 100.0 * $maxlen; printf " \t\tDone:\t[%-".$maxlen."s] (%d%%) ", "=" x $barlen, $percent; } ReadMode 0; # Reset tty mode before exiting kill 15, $mypid; wait; close (FIFO); print "\n\n"; }; $info = sub { my ($string, $what, $where, $count) = @_; $where =~ s/^\s//; $where =~ s/\s$//; my %id = id3($where, $what); print "\tTitle:\t $id{'title'}\n"; print "\tArtist:\t $id{'artist'}\n"; print "\tAlbum:\t $id{'album'}\n"; print "\tYear:\t $id{'year'}\n"; print "\tComment: $id{'comment'}\n"; print "\tFile:\t $where\n\n"; }; while ( defined ($_ = $term->readline($prompt)) ) { $term->addhistory($_) if /\S/; if (/^q(?:uit)?/ or /^e(?:xit)?/) { print "Bye !\n"; exit 0; } elsif (/^h(?:elp)?/) { help } elsif (/^c(?:lear)?/) { system("clear"); } elsif (/^p(?:lay)?\s(.*)/) { step $1, $play; } elsif (/^l(?:list)?\s(.*)/) { step $1, $list; } elsif (/^i(?:nfo)?\s(.*)/) { step $1, $info; } elsif (/^a(?:dd)?\s(.*)/) { print $OUT "Adding directory $1 to index\n"; &find($1); } elsif (/^d(?:rop)?/) { print $OUT "Dropping index\n"; untie %playlist_ba; untie %playlist_bn; unlink $ba_file; unlink $bn_file; $ba_h = tie %playlist_ba, "DB_File", $ba_file, O_RDWR|O_CREAT, 0640, $DB_BTREE; $bn_h = tie %playlist_bn, "DB_File", $bn_file, O_RDWR|O_CREAT, 0640, $DB_BTREE; } } sub quit { ReadMode 0; untie %playlist_ba; untie %playlist_bn; unlink $fifo; system ("rm -f /tmp/*.$ext"); system ("clear"); exit; } __END__ =head1 NAME mp3shell - A shell like interface to mpg123 and id3 tags =head1 SYNOPSIS B C<[ -c F ]> =head1 DESCRIPTION B allows you to manage and hear your mp3s comfortably using a shell like interface. The first time you use B, you should invoke it with the C<-c> switch. B gives you a shell prompt, where you can type in the following commands: =over 4 =item C Lists all mp3s, whiches filename and/or author and group id3 tag contains perl regular expression C. =item C Shows infos (id3 tags and filename) about files matching C. =item C Plays all files matching C. If you press 'n' while playing a mp3, B makes you jump to the next file. Pressing 'p' will exit mp3shell. =item C Adds the directory beetween the two slashes to your index. =item C Drops your index. =item C Clears your screen. =item C Gives you an useless help screen. =item C Exits mp3shell. =item C Same as quit. =back You can shorten all command to their first character. You can leave away the slashes surrounding C. These are not bugs, but features ;) =head1 OPTIONS =over 4 =item C<-c F> Create an index by searching for mp3s in F and its subdirs. =back =head1 AUTHOR Christoph Hellwig I =head1 THANKS =over 4 =item 'Optonic' aka Kristof Korwisi I For beta testing C and setting up the website (http://www.quarzen.de/mp3shell/). =item Michael Hipp I For coding mpg123, the best mp3 player on earth. =back =head1 TODO =over 4 =item http streaming =item napster support ? =item automatic ftp downloads ? =back