#!/usr/bin/perl -w # Written and maintained by: # Karl Dahlke # eklhad@comcast.net # 248-524-1004 (during regular business hours) # http://www.eklhad.net # Most Linux distributions include cdparanoia, for pulling songs off your cds, # and mpg123, for playing mp3 files. # My redhat distribution did not include an mp3 encoder. # An excellent encoder, bladeenc, is available at # http://www.mp3-converter.com/encoders/bladeenc.htm # jukebox is a program to play songs somewhat at random. # This can be run in the background, like a song daemon, # but then you'll have to kill it by hand, so to speak. # Or you can run it in the foreground on one of your 6 consoles. # If the latter, ^c skips the current song, which perhaps you don't like, # and moves on to the next song. # to see how to set up this program, check out the sample .jukerc file, # www.eklhad.net/linux/app/sample.jukerc use IO::Handle; $home = $ENV{"HOME"}; defined $home and length $home or die '$HOME is not defined in the environment - I don\'t know how to find your .domailrc file.'; $jukerc = "$home/.jukerc"; open FH, $jukerc or die "Cannot open config file $jukerc."; $songdir = ""; $songext = "mp3"; # %subdirs $hitweight = 2; $catweight = 2; $player = ""; while() { s/^\s+//; s/\s*#.*$//; next if /^$/; s/\s*$//; if(/^([^=]+)=\s*([^=]+)/) { my $key = $1; my $value = $2; $key =~ s/\s+$//; if($key eq "songdir") { $songdir = $value; -d $songdir or die "$songdir is not a directory."; next; } if($key eq "hits") { $value =~ /^\d+$/ or die "hits value should be an integer."; $hitweight = $value; $catweight = $value; next; } if($key eq "player") { $player = $value; length $player or die "missing mp3 player"; next; } if($key eq "songext") { $songext = $value; next; } if(length $key == 1) { $subdirs{$key} = $value; next; } die "Unrecognized keyword <$key> in config file."; } die "garbled line <$_> in config file."; } close FH; length $songdir or die "song directory not specified."; %subdirs or die "no subdirectories defined."; $showlist = 0; if($#ARGV == 1 && $ARGV[1] =~ /^\d+$/) { $showlist = $ARGV[1]; pop @ARGV; } if($#ARGV != 0) { print "usage: jukebox letter[+]letter[+]letter[+]... [count]\n"; exit 1; } $categories = $ARGV[0]; $categories =~ s/\++/+/g; $categories =~ s/^\+//; @songlist = (); @songindex = (); $idxlevel = 0; if($categories =~ s/^@//) { # categories is now the name of a group/artist foreach my $s (glob "$songdir/*/$categories*[!-].$songext") { next if $s =~ m,/\*/,; # no match next if $s =~ m,/xmas/,; # christmas push @songlist, $s; push @songindex, $idxlevel; my $increment = 1; $increment = $hitweight if $s =~ /\+\.mp3$/; $idxlevel += $increment; } } else { for(my $i = 0; $i < length $categories; ++$i) { my $c = substr $categories, $i, 1; next if $c eq '+'; my $subdir = $subdirs{$c}; defined $subdir or die "character $c is not mapped to a subdirectory in your config file."; -d "$songdir/$subdir" or die "$songdir/$subdir is not a directory."; my @list = glob "$songdir/$subdir/*[!-].$songext"; next if $#list == 0 and $list[0] eq "$songdir/$subdir/*[!-].mp3"; push @songlist, @list; } for(my $i = 0; $i < length $categories; ++$i) { my $c = substr $categories, $i, 1; next if $c eq '+'; my $subdir = $subdirs{$c}; foreach my $song (@songlist) { next unless $song =~ m,^$songdir/$subdir/[^/]*$,; push @songindex, $idxlevel; my $increment = 1; $increment = $hitweight if $song =~ /\+\.mp3$/; $increment *= $hitweight if $i < length($categories)-1 and substr($categories, $i+1, 1) eq '+'; $idxlevel += $increment; } } } push @songindex, $idxlevel; print(($#songlist+1)." songs to choose from\n"); exit 0 if $#songlist < 0; $memory = int $#songlist/2; $lastsong = ""; sub randomSong() { my $goal; choice: { $goal = int rand $idxlevel; my ($i, $l, $r); # binary search $l = -1, $r = $#songindex; while($r - $l > 1) { $i = ($l+$r+1) / 2; if($songindex[$i] <= $goal and $songindex[$i+1] > $goal) { redo choice if $songlist[$i] eq $lastsong and $#songlist; return $songlist[$i] } if($songindex[$i] > $goal) { $r = $i; } else { $l = $i; } } } die "song with index $goal not found."; } # randomSong $horizon = 50; # check against the last 50 songs @hist = (); # history of songs $jukelist = "$home/.jukelist"; if(open FH, $jukelist) { while() { s/\n//; unshift @hist, $_; } close FH; $#hist = $memory-1 if $#hist >= $memory; } sub fitness($) { my $song = shift; my ($type, $artist) = $song =~ m,^(.*)/([^./-]+)[^/]*$,; my $max = 0; for(my $i = 0; $i <= $#hist; ++$i) { my $song1 = $hist[$i]; return -1 if $song1 eq $song; my $multiplier = $horizon - $i; next if $multiplier <= 0; my $val = 0; my ($type1, $artist1) = $song1 =~ m,^(.*)/([^./-]+)[^/]*$,; $val += 1 if $type eq $type1; $val += 2 if $artist eq $artist1 and $artist ne "misc"; $val *= $multiplier; $max = $val if $val > $max; } return $max; } # fitness $showcount = 0; newsong: { my $bestfit = 10*$horizon + 1; # big number my $bestsong = ""; my $tries = 0; while($tries < 4) { my $song = randomSong(); my $fit = fitness($song); next if $fit < 0; # already heard this one $bestsong = $song, $bestfit = $fit if $fit < $bestfit; ++$tries; } unshift @hist, $bestsong; $lastsong = $bestsong; $#hist = $memory-1 if $#hist >= $memory; print "$bestsong\n"; if($showlist) { exit if ++$showcount == $showlist; } else { if(open FH, ">>$jukelist") { print FH "$bestsong\n"; close FH; } system "$player $bestsong"; } redo newsong; } # loop selecting songs