http://qs321.pair.com?node_id=165048

Problem: I have a 40gb HD on an old P/133 running Debian that serves as my mp3 server. It is plugged into the stereo, so I can listen for days without having to mess with the stereo. This is not the problem.

The problem is that half the time I have no clue what I'm listening to. The server has no monitor, so I operate it via ssh, but I'm not *always* at the controlling terminal either. I download a lot of music from emusic.com and occasionally I will borrow a CD (from a friend or the library) and rip it. So my collection of mp3s is significantly larger than my memory.

Now I could use MP3::Info to list the songs to the terminal as they play, but that still requires me to keep an open terminal, and to run to the computer to see what's playing if I want to know. Better would be a way that would allow me to get song information at the same time I get the songs. You know, like on the radio.

Enter Compu Kasem. Through the magic of speech synthesis and a little Perl glue, the cyber DJ accepts a playlist (optional shuffle), announces the songs before and after they play, as well as the time (again, easier than hunting down a clock). Requires the Festival speech synthesizer and uses the 'aumix' audio utility to set the volume. Also, for now, works only with mp3 files (sorry Ogg-philes) via the mpg123 (or mpg321) application/library.

Also, while a song is playing, you can use the following commands:
. increase volume
, decrease volume
(space) skip song
1234567890 set volume to 10% to 100% (in 10% increments)

To do list includes: build set of routines for speech functions, include variety of song introduction/recap phrases, only announcing time in fifteen minute intervals, perhaps grabbing news headlines and/or weather information and inserting those between songs.

Update: of course there are several modules for dealing with Festival servers (i.e. running festival as a daemon rather than piping to it, as I do here). So this is on the to-do list, because it will probably cause less lag (as starting the festival client after before each song is a bit slow).
#!/usr/bin/perl -w use strict; use Audio::Play::MPG123; use MP3::Info; use Term::ReadKey; use Getopt::Long; my $shuffle; GetOptions( "shuffle" => \$shuffle ); my $DEBUG = 1; my $max_vol = 100; my $VOLUME = 70; my $speech_speed = 3; #a single digit between 0 and 9 my @playlist; if( @ARGV ) { @playlist = @ARGV; } else { say_error( "No playlist given. Please select some songs for me to p +lay!" ); } if( $shuffle ) #Shuffle songs in playlist { #fisher-yates shuffle from Perl Cookbook my $array = \@playlist; my $i; for( $i = @$array; --$i; ) { my $j = int rand ($i+1); next if $i==$j; @$array[$i,$j] = @$array[$j,$i]; } } my $key; my( $this_artist, $last_artist ); my( $this_album, $last_album ); my( $this_song, $last_song ); my( $this_year, $last_year ); ReadMode 3; # Turn off controls keys for( @playlist ) { print "Starting song '$_'\n" if $DEBUG; my $song = MP3::Info->new( $_ ); my $this_artist = $song->artist; my $this_album = $song->album; my $this_song = $song->title; my $this_year = $song->year; my $prev_vol = $VOLUME; set_vol( $max_vol ); print "Doing DJ routine\n" if $DEBUG; open( VOICE, "| festival --pipe" ) or die "$!"; print VOICE "(voice_rab_diphone)"; print VOICE "(Parameter.set 'Duration_Stretch 1.$speech_speed)"; if( $last_song ){ print VOICE "(SayText \"That was $last_artist with the song $last_ +song\")"; my $the_time = get_time(); print VOICE "(SayText \"The time is now $the_time\")"; } print VOICE "(SayText \"Here is $this_song played by $this_artist\ +")"; close VOICE; $last_artist = $this_artist; $last_album = $this_album; $last_song = $this_song; $last_year = $this_year; set_vol( $prev_vol ); print "Running player for this song\n" if $DEBUG; my $player = new Audio::Play::MPG123; $player->load( $_ ); my $stopper = 0; until( $player->state == 0 or $stopper) { unless (not defined ($key = ReadKey(-1))) { if( $key =~ /^\d$/ ) { $key = 10 unless $key; set_vol( $key * 10 ); } elsif( $key eq '.' ) { set_vol( $VOLUME + 1 ); } elsif( $key eq ',' ) { set_vol( $VOLUME - 1 ); } elsif( $key eq ' ' ) { print "Skipping to next song\n" if $DEBUG; $player->stop; $stopper = 1; next; } } $player->poll(1); } } ReadMode 0; # Reset tty mode before exiting sub say_error { my $err_msg = shift or die "No error given: $!"; my $prev_vol = $VOLUME; set_vol( $max_vol ); open( VOICE, "| festival --pipe" ) or die "$!"; print VOICE "(voice_rab_diphone)"; print VOICE "(Parameter.set 'Duration_Stretch 1.$speech_speed)"; print VOICE "(SayText \"$err_msg\")"; close VOICE; set_vol( $prev_vol ); } sub get_time { my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime +(time); my $time; if( $min == 0 && $hour == 0 ) { $time = "midnight"; } elsif( $min == 0 && $hour == 12 ) { $time = "noon"; } elsif( $hour < 12 && $min <= 30) { $time = "$min minutes after $hour in the morning"; } elsif( $hour < 12 && $min > 30) { my $bmin = 60 - $min; $hour++; $time = "$bmin minutes before $hour in the morning"; } elsif( $hour == 12 && $min <= 30) { $time = "$min minutes after $hour in the afternoon"; } elsif( $hour == 12 && $min > 30) { my $bmin = 60 - $min; $hour++; $time = "$bmin minutes before $hour in the afternoon"; } elsif( $hour > 12 && $min <= 30) { $hour -= 12; $time = "$min minutes after $hour in the morning"; } elsif( $hour > 12 && $min > 30) { $hour -= 12; my $bmin = 60 - $min; $hour++; $time = "$bmin minutes before $hour in the morning"; } else { $time = "I have no idea what time it is!"; } return $time } sub set_vol { $VOLUME = shift || return; $VOLUME = 100 if $VOLUME >= 100; $VOLUME = 0 if $VOLUME < 0; system( "aumix -v $VOLUME" ); }

Replies are listed 'Best First'.
Re: Compu Kasem (a Cyber-DJ)
by mcstayinskool (Acolyte) on May 10, 2002 at 13:57 UTC
    This is way cool. It took me nearly an hour to compile the festival stuff, but amazingly it worked without much of any hacking. I added a little subroutine to pull in playlists from .m3u files (which are basically lists of paths to files). Here's the idea...
    my @playlist; if ($ARGV[0] =~ /\.m3u$/i) { @playlist = fill_playlist($ARGV[0]); } elsif( @ARGV ) { @playlist = @ARGV; } else { say_error( "No playlist given. Please select some songs for me to p +lay!" ); } sub fill_playlist { my $playlist = shift; my @return; if (-r $playlist) { open(LIST,"<$playlist") or die "couldn't open $playlist: $!"; while(<LIST>) { next if /^#/; chomp; push @return, $_; } close(LIST); } else { say_error( "There was a problem accessing this playlist. Pleas +e select some songs for me to play!" ); exit; } return @return; }
Re: Compu Kasem (a Cyber-DJ)
by elwarren (Priest) on May 08, 2002 at 21:10 UTC
    You could implement your time announcement every 15 minutes by just announcing it every three or four songs instead trying to parse the time. Given an average song length of 5 minutes or so this would work, but if you listen to alot of Ramones then you might want to up that number to something more like every twelve songs ;^}
Re: Compu Kasem (a Cyber-DJ)
by belden (Friar) on May 09, 2002 at 19:49 UTC
    ++ichimunki for reminding me just how cool open-source software can be! One of the things that's cool about the monastery is that I find out about different open source projects that I wouldn't have found myself but which are interesting to read about, even if I don't end up installing the software.

    This is actually something that I might be able to use in the near future, though: I'm getting married in late June, and my fianceé and I are thinking about having some music at the banquet - but we're too cheap to spring for a living DJ. It looks like with a little work, we can set up my Linux laptop to do the DJing for us! Looks like a good evening/weekend project for my near future...

    I'll be sure to let you know if we really do this: your code looks like a great starter for what we'd need! Thanks for the post - this really is a cool use for Perl.

    blyman
    setenv EXINIT 'set noai ts=2'

      Better fire up gnutella and start looking for the Chicken Dance!!!
Re: Compu Kasem (a Cyber-DJ)
by otterfish (Initiate) on May 15, 2002 at 06:53 UTC
    Brilliant! Definitely a "Cool Use For Perl"!

    I had to make a couple of changes to get things working smoothly. I've moved the following code above the for loop as it kept spawning new processes of mpg123 every iteration and grinding my machine to a halt:

    my $player = new Audio::Play::MPG123;

    Also, none of my MP3s have ID3 tags (because I remove them with a passion!). All my files follow the format "Artist - Song Title.mp3" so I've added the following in place of the MP3::Info so that CompuKasem can do his thing:

          if ($_ =~ m/(a-zA-Z0-9\ +)\ \-\ (a-zA-Z0-9\ +)\.mp3/)
          {
            $this_artist = $1;
            $this_song = $2;
          }
    

    Now all that's needed is "Gillian Anderson" voice file and I'm set! Cheers!
      Odd that you should have to move that object out of the loop. I'll have to check my process list to make sure I don't have an abundance of mpg321s around myself. Since the object goes out of scope after each song, one would hope that would kill the process... or that the process would die when it is done playing the file... or both. On my machine, if I didn't do that I seemed to have trouble getting /dev/dsp free for festival again. Thanks for pointing this out. I'll have to look into it.
      I had the same problem. When moving the call to MPG123 outside the loop it solved the problem, except now mpg123 goes nuts after playing the last song. Any ideas? Otherwise, this was a very fun thing to try out - I think I will actually use it when I get my mp3 machine setup at home. Thanks....
        ahhh, read the documentation you say? Yes, the docs for Audio::Play::MPG123 say to use the version of mpg123 that comes with the module! I did that and the problem went away. You can now put the call to my $player = new Audio::Play::MPG123; inside the loop again.