Rationale
I (peterc) hate my city's radio stations. Shock jocks are mind-numbing, public radio runs the risk that they might be broadcasting parliamentary procedings (more likely to put me back to sleep than wake me up), and the other stations are just tedious.
Cron + a sound card + Tcl MP3 selector script + mpg123 + a 5 metre headphone extension cable + external PC speakers + mp3 files = No more accidental sleep-ins from poor breakfast radio!
mp3select.sh script
#!/usr/local/ActiveTcl-8.5/bin/tclsh8.5 package require fileutil ######## # Essentials proc shuffle {list} { set n 1 set slist {} foreach item $list { set index [expr {int(rand()*$n)}] set slist [linsert $slist $index $item] incr n } return $slist } proc lvarpop {upVar {index 0}} { upvar $upVar list; if {![info exists list]} { return "-1" } set top [lindex $list $index]; set list [concat [lrange $list 0 [expr $index - 1]] [lrange $list [expr $index +1] end]] return $top; } ######## # General proc get_dirs {} { if {![file exists $::dirs_file]} { puts "Error: $::dirs_file does not exist!" ; exit } if {![file readable $::dirs_file]} { puts "Error: $::dirs_file unreadable!" ; exit } set DF [open $::dirs_file "r"] fconfigure $DF -encoding binary -translation binary -buffering none set dirs [read $DF] close $DF return $dirs } proc random_from_all {items} { # Create list set fl [list] set dirs [get_dirs] foreach d $dirs { if {![file exists $d]} { continue } ;# Ignore moved/missing directories. set mp3 [fileutil::findByPattern $d *.mp3] foreach f $mp3 { lappend fl $f } # Extra filetypes. foreach ext $::othermedia { set mfiles [lsort [fileutil::findByPattern $d -glob *.${ext}]] foreach f $mfiles { lappend fl $f } } } # puts "Full list:\n$fl\n"; set fl [shuffle $fl] set c 0 ; while {$c < $items} { lappend pl [lvarpop fl 0] ; incr c } return $pl } proc ordered_from_random_dirs {items} { # Create list set fl [list] set dl [list] set dq [list] set pl [list] set dirs [get_dirs] foreach d $dirs { set found [fileutil::findByPattern $d *] foreach f $found { if {[file isdirectory $f] && [regexp $d $f] } { lappend dl $f } } } # Shuffle directories set dl [shuffle $dl] set c 0 ; while {$c < $items} { lappend dq [lvarpop dl 0] ; incr c } # Collect files inside directories foreach d $dq { # Standard MP3 set mp3 [lsort [fileutil::findByPattern $d -glob *.mp3]] foreach f $mp3 { lappend pl $f } # Extra filetypes. foreach ext $::othermedia { set mfiles [lsort [fileutil::findByPattern $d -glob *.$ext]] foreach f $mfiles { lappend pl $f } } } return $pl } proc set_directories_file {value} { set ::dirs_file $value } proc set_toggle_ext {key} { # Toggle on or off. if {[lsearch $::othermedia $key] < 0} { lappend ::othermedia $key } else { lvarpop ::othermedia [lsearch -exact $::othermedia $key] } return ok } ######## # Main interp alias {} -rdof {} ordered_from_random_dirs ;# Random Dirs, Ordered Files interp alias {} -rdrf {} random_from_all ;# Random Dirs, Random Files interp alias {} -cf {} set_directories_file ;# Sets the root dirs file. interp alias {} -togext {} set_toggle_ext ;# Set using extname; toggles set ::dirs_file "~/.mp3s/default.cf" set ::othermedia [list] foreach {opt value} $argv { foreach f [$opt $value] { if {[string length $f] > 3} { puts $f ; set didsomething 1 } } } if {![info exists didsomething]} { puts "Syntax:\n" puts " mp3select.sh opt value opt value \[...\]\n" puts "Options:\n" puts " -rdof X : Random Dirs, Ordered Files; to play X random audioplays." puts " -rdrf X : Random Files; for playing X random tracks." puts " -cf /path/file.cf : Sets the path to the file with root directories." puts " -togext ext : Toggle the inclusion of *.ext files.\n" puts "Example:\n" puts " mp3select.sh -rdrf 10 -cf ~/punk.cf -rdof 3\n" puts " This selects 10 random songs from the dirs in the default file," puts " changes the dirs file to ~/punk.cf then selects 3 random albums.\n" puts "Notes:\n" puts " * mp3select.sh will not select a song twice in the same option track" puts " selection." puts " * The -cf setting applies to the next -rdof/-rdrf file selection/s" puts " but not previous ones. Multiple -cf may be used." puts " * Default file for listing directories: ~/.mp3s/default.cf\n" }
default.cf (and other conf/cf files)
Each file contains a list of root directories for your MP3 files, one per line. eg:
/home/someuser/Music /data/Music
These root directories are searched recursively for playable MP3 files. You can list an unlimited number of root directories in each file, and of course, specify any number of conf/cf files on the command-line.
Use examples
Set up in Cron (using crontab -e):
0 7 * * * * /pathto/mp3select.sh -togext m4a -rdrf 30 >/tmp/pl.txt && mplayer -af volnorm -really-quiet -playlist /tmp/pl.txt >/dev/null 2>&1
I originally used 'mpg123 -q -@ /tmp/pl.txt' for reading playlists from a file, but, the range of sound levels in my MP3s made me appreciate mplayer's volume normalisation capability.
Changes
peterc 2008-12-12: Now won't fail if one of the directories in your root dirs conf is missing. Now also supports audio of any type by using an extensions toggle (-togext ext). Call once to include, a second time to remove (personally, I use this as I have a few m4a files). The default root dirs conf is now ~/.mp3s/default.cf.
See also AK's A musicbox.