randomplay-0.60+nmu1/0000755000000000000000000000000011740642526011356 5ustar randomplay-0.60+nmu1/README0000644000000000000000000000411210341222344012220 0ustar Randomplay is a 'scratch an itch' music file player. The primary itch was to be able to play a large music collection in random order but not to have tracks repeated if the player was shut down, or if new tracks were added to the collection, etc.. Another itch was to be able to specify a song to play quickly without having to track it down in a directory hierarchy. I also wanted to be able to use randomplay to generate a list of songs to load into a portable MP3 player, using the same memory of what has been played recently on the computer itself. The final itch was to have the random shuffle be biased toward favorite songs. Randomplay scratches all these itches, and some other ones too. This is a command line tool, for people who eschew GUIs when it comes to playing their music files. Many of the features in randomplay exist in other GUI music players, but I am not aware of any command-line only player that includes every feature of randomplay. You will need an actual music file player as well--randomplay is just a frontend. Randomplay defaults to using ogg123 for ogg files and mpg321 for mp3 files, but you can specify other programs at the command line or in the ~/.randomplayrc file (for example, player[wav]=play-sample). A properly built mplayer could probably be used for all music file types. Invoking randomplay is straightforward. See the manpage or the help options (randomplay --help) for a list of command line options and keybindings while randomplay is running. You can also set all options permanently in a ~/.randomplayrc file. See the sample randomplayrc that comes with this package for the syntax. I recommend creating aliases or shell scripts for common switch combinations that you use. For example, I have a script 'rpr': #!/bin/sh randomplay -0 --noremember -r '$@' You could use this to quickly play a particular track based on a fragment of its filename, regardless of where it is in your music directory hierarchy (assuming you have defined "basedir" in ~/.randomplayrc). I welcome bug reports and comments. Adam Rosi-Kessel randomplay-0.60+nmu1/copyright0000644000000000000000000000025310514467150013305 0ustar randomplay is copyright 2003-2006 by Adam Kessel. Permission is granted to redistribute under the terms of the GNU General Public License v2.0 or later, at your choice. randomplay-0.60+nmu1/TODO0000644000000000000000000000130010407324016012026 0ustar Possibly switch to XML-format configuration file. Should probably clean up ~/.randomplay_history eventually--the file currently grows indefinitely. Maybe check for free disk space before writing history file, although an increase of 10-15 bytes disk usage per song played is pretty minimal. Possibly build an index of available files so the whole directory tree doesn't need to be traversed every time randomplay is executed; this can take a long time with a large directory tree or over NFS. Advantage of the current system is new songs become immediately available and there is no structural overhead. Maybe have --(re)build-index and --use-index options or something like that. Random album feature randomplay-0.60+nmu1/Makefile0000644000000000000000000000125610111241175013004 0ustar PREFIX=/usr INSTALL=/usr/bin/install BINDIR=$(DESTDIR)$(PREFIX)/bin MANDIR=$(DESTDIR)$(PREFIX)/share/man/man1 DOCDIR=$(DESTDIR)$(PREFIX)/share/doc/randomplay none: man: randomplay pod2man randomplay > randomplay.1 install: randomplay randomplay.1 changelog.gz README TODO randomplayrc $(INSTALL) -d $(BINDIR) $(MANDIR) $(DOCDIR) $(INSTALL) -m 755 randomplay $(BINDIR) $(INSTALL) -m 644 randomplay.1 $(MANDIR) $(INSTALL) -m 644 changelog.gz README TODO randomplayrc $(DOCDIR) uninstall: rm $(BINDIR)/randomplay rm $(MANDIR)/randomplay.1 rm -r $(DOCDIR) all: man install clean: randomplay.1 rm randomplay.1 none: man @echo "Now run make install to install randomplay." randomplay-0.60+nmu1/debian/0000755000000000000000000000000011740642553012600 5ustar randomplay-0.60+nmu1/debian/compat0000644000000000000000000000000207746120512013773 0ustar 4 randomplay-0.60+nmu1/debian/control0000644000000000000000000000242711740642546014212 0ustar Source: randomplay Section: sound Priority: optional Maintainer: Adam Rosi-Kessel Build-Depends: debhelper (>> 4.0.0) Standards-Version: 3.7.2 Package: randomplay Section: sound Architecture: all Depends: ${perl:Depends}, libdate-calc-perl, libterm-readkey-perl, libmp3-info-perl, libogg-vorbis-header-pureperl-perl, libdate-manip-perl Recommends: vorbis-tools, mpg321 Suggests: xosd-bin Description: command-line based shuffle music player that remembers songs between sessions Randomplay plays your music collection (or execute any arbitrary commands on any arbitrary filetypes) in random order, remembering songs played across sessions. It also has many features to make command-line music playing more convenient, including recursive regexp searching for tracks and the ability to specify a certain number of tracks, bytes, or minutes to play. Randomplay will also generate a list of music files to be loaded onto a portable music player device. It includes a 'random weighting' feature, so your favorite songs are more likely to come up in the random shuffle. . Randomplay is a convenient tool for the user who does everything in an xterm window or console and is constantly devising complex find/grep/sed command lines to play just the right set of songs. randomplay-0.60+nmu1/debian/copyright0000644000000000000000000000066110407333135014526 0ustar This package was originally created by Adam Rosi-Kessel on Sun, 7 Sep 2003 17:59:00 -0600 The original can be found at . randomplay is copyright 2003-2006 by Adam Rosi-Kessel. Permission is granted to redistribute under the terms of the GNU General Public License v2.0 or later, at your choice. See the file /usr/share/common-licenses/GPL for more information. randomplay-0.60+nmu1/debian/changelog0000644000000000000000000002041211740642422014444 0ustar randomplay (0.60+nmu1) unstable; urgency=low * Non-maintainer upload. * Use Ogg::Vorbis::Header::PurePerl instead of Ogg::Vorbis::Header. (Closes: #655410) -- Tim Retout Mon, 09 Apr 2012 21:21:41 +0100 randomplay (0.60) unstable; urgency=low * New options --older-than and --newer-than, to only play files whose modification times are respectively older or newer than given date or time interval (e.g., --newer-than 1Y = files newer than one year old). * Allow alternative forms of expressing 'last played by' date for --days option as with --older-than and --newer-than. -- Adam Rosi-Kessel Sun, 15 Oct 2006 13:09:06 -0400 randomplay (0.53) unstable; urgency=low * Update contact name/address and copyright dates in documentation. -- Adam Rosi-Kessel Sun, 19 Mar 2006 14:52:04 -0500 randomplay (0.52) unstable; urgency=low * Fix typo in sample randomplayrc for new 'back key' feature. -- Adam Rosi-Kessel Sun, 19 Mar 2006 14:33:57 -0500 randomplay (0.51) unstable; urgency=low * Change architecture to all. (Closes: #357050). * New feature: keybinding to go backward through previously played songs during session. Default key is 'b'. (Closes: #351295). -- Adam Rosi-Kessel Thu, 16 Mar 2006 10:44:24 -0500 randomplay (0.50) unstable; urgency=low * Show weight of current song when playing (thanks again to Gregor Herrmann). (Closes: #351289). -- Adam Rosi-Kessel Tue, 7 Feb 2006 20:48:38 -0500 randomplay (0.49) unstable; urgency=low * Add support for toggle "pause" key, default 'p'. Thanks to Simon Hart for the patch. * Add support for enabling/disabling UTF-8 output in MP3 tags, for people who don't have UTF-8 xterm. Thanks to Gregor Herrmann for the patch. (Closes: #337783). * Add new keystroke to list available keystrokes while playing, default '?' or 'h'. * Improved sample randomplayrc with more comments and options. You can use the sample randomplayrc as a template for all settings. -- Adam Rosi-Kessel Wed, 23 Nov 2005 21:21:40 -0500 randomplay (0.48) unstable; urgency=low * When advancing tracks, kill not just children of player program, but all spawned processes. Thanks to Christopher Zimmermann for the patch. (Closes: #335592). -- Adam Rosi-Kessel Sun, 30 Oct 2005 08:38:19 -0500 randomplay (0.47) unstable; urgency=low * Display track information with an announcement program -- default is xosd. -- Adam Rosi-Kessel Thu, 1 Sep 2005 11:36:09 -0400 randomplay (0.46-1) unstable; urgency=low * If a specified player is not available, just don't play files of that type, rather than halt program all together (except if no players are available). * Update documentation. * Remove sound-recorder recommendation; if you want to play anything other than mp3 and ogg files, you should choose the players you prefer. -- Adam Rosi-Kessel Sun, 5 Sep 2004 10:06:57 -0400 randomplay (0.45-1) unstable; urgency=low * Remove WAV file player from defaults; most people don't include WAVs in their music collections (I think!) and this way randomplay will start in the default configuration even if you don't have sound-recorder installed. (Closes: #269541). * Fix bug where only alphabetical (rather than alphanumeric) extensions could be configured in .randomplayrc; now you can define a player for mp3 files. (Closes: #269540). -- Adam Rosi-Kessel Wed, 1 Sep 2004 21:46:25 -0400 randomplay (0.44-1) unstable; urgency=low * Rewrite option processing to eliminate unnecessary variables (i.e., "$norandom") thanks to Long::Getopt "negate!" option. * Reorganize options on manpage into sensible groups. -- Adam Rosi-Kessel Tue, 24 Aug 2004 20:31:03 -0400 randomplay (0.43-2) unstable; urgency=low * Another failed upload, sorry for the version bloat! -- Adam Kessel Mon, 23 Aug 2004 20:54:04 -0400 randomplay (0.43-1) unstable; urgency=low * Remove spurious "players" debug message on startup. -- Adam Kessel Mon, 23 Aug 2004 14:46:42 -0400 randomplay (0.42-2) unstable; urgency=low * Failed upload again. -- Adam Kessel Fri, 20 Aug 2004 15:42:37 -0400 randomplay (0.42-1) unstable; urgency=low * Fix errors in sample randomplayrc. * Add more flexibility to randomplayrc interpretation; can add arbitrary whitespaces anywhere without harm, and make ( [ { interchangeable. * Check to make sure base directory exists, if specified. * Keystrokes are now user-definable at command line or in ~/.randomplayrc. * Should look for files with mp3 extension, not mpg extension. * Update documentation. -- Adam Kessel Fri, 20 Aug 2004 11:50:15 -0400 randomplay (0.41-3) unstable; urgency=low * One more upload. -- Adam Kessel Thu, 19 Aug 2004 20:38:29 -0400 randomplay (0.41-2) unstable; urgency=low * Failed upload, trying again. -- Adam Kessel Thu, 19 Aug 2004 20:35:22 -0400 randomplay (0.41-1) unstable; urgency=low * Should not be native package; fixed versioning. -- Adam Kessel Thu, 19 Aug 2004 19:18:32 -0400 randomplay (0.40-2) unstable; urgency=low * Fixed some minor linda/lintian warnings. -- Adam Kessel Thu, 19 Aug 2004 18:56:18 -0400 randomplay (0.40-1) unstable; urgency=low * Initial Debian release. (Closes: #266919). -- Adam Kessel Thu, 19 Aug 2004 16:46:01 -0400 randomplay (0.39-1) unstable; urgency=low * New random weighting functionality: hit + or - while a song is playing to increase or decrease its probability of being played in future random shuffles, or 0 or = to reset preference. * Improved randomization algorithm: should really be random now. * New switch --weight/--noweight to specify whether or not to use random weighting. * New switch --verbose; if not specified then output from music player programs is suppressed. * New behavior for --quiet: if specified, randomplay will not report the name of each song as it is played. * Removed --altcancel option; it is no longer necessary now that you can advance songs or cancel randomplay with keystrokes. * Display track metadata before each track is played if available; otherwise display filename. * Updated manpage for new options and keystrokes while song is playing * Improved documentation; include README, TODO, and sample randomplayrc, fixed some erroneous references in Debian documentation. * Fixed makefile so it works without dpkg-buildpackage. * Fixed a bunch of warnings that appear when perl -w is used. * Added option to quit after the current playing song is done (default key 'l'). * Can now process arbitrary filename extensions, either at command line or in ~/.randomplayrc, so could potentially be used for activities other than playing music. * Implement hack to kill children processes when randomplay is run from shell script; this will likely limit randomplay's portability. * Check that players are in path and executable before playing. * Add in several more error/sanity checks for file permissions. -- Adam Kessel Thu, 19 Aug 2004 12:41:23 -0400 randomplay (0.35-1) unstable; urgency=low * New upstream release. * --days=0 / -0 option now actually means "0 days," not one day. -- Adam Kessel Sat, 17 Jul 2004 10:06:25 -0400 randomplay (0.34-1) unstable; urgency=low * New upstream release. * Fixed ogg321 -> ogg123 default ogg player. * New "--version" switch. -- Adam Kessel Fri, 16 Jul 2004 10:32:17 -0400 randomplay (0.33-1) unstable; urgency=low * New upstream release. -- Adam Kessel Thu, 23 Oct 2003 20:49:30 -0700 randomplay (0.32-1) unstable; urgency=low * New upstream release. -- Adam Kessel Sat, 20 Sep 2003 15:11:08 -0700 randomplay (0.3-1) unstable; urgency=low * Initial public release. -- Adam Kessel Sun, 7 Sep 2003 15:01:54 -0700 randomplay-0.60+nmu1/debian/docs0000644000000000000000000000003110105671710013434 0ustar TODO README randomplayrc randomplay-0.60+nmu1/debian/rules0000755000000000000000000000146710111241265013652 0ustar #!/usr/bin/make -f configure: configure-stamp configure-stamp: dh_testdir touch configure-stamp build: build-stamp build-stamp: configure-stamp dh_testdir $(MAKE) pod2man randomplay > randomplay.1 touch build-stamp clean: dh_testdir dh_testroot rm -f build-stamp configure-stamp -$(MAKE) clean dh_clean install: build dh_testdir dh_testroot dh_clean -k dh_installdirs $(MAKE) install DESTDIR=$(CURDIR)/debian/randomplay binary-indep: build install binary-arch: build install dh_testdir dh_testroot dh_installchangelogs dh_installdocs dh_installexamples dh_installman dh_link dh_compress dh_fixperms dh_perl dh_installdeb dh_shlibdeps dh_gencontrol dh_md5sums dh_builddeb binary: binary-indep binary-arch .PHONY: build clean binary-indep binary-arch binary install configure randomplay-0.60+nmu1/randomplayrc0000644000000000000000000000351010514466152013770 0ustar # This is a sample randomplayrc, copy it into your home directory as ~/.randomplayrc # base directory from which music will be searched if the specified directory or filename doesn't exist # in the current directory, or if no directory is specified basedir ~/music # history file history ~/.randomplay_history # how many days a song must have last been played before it can be played again days=10 # only play songs with modification time newer than last six months # newer-than=6M # only play songs with modification time before 1/1/2006 # older-than=1/1/2006 # hide output from player programs verbose=no # show the track information before each track is played quiet=no # default to remember songs played remember=yes # print tags in UTF-8 format utf8=yes # default to random shuffle random=yes # pause between songs, default to one second pause=1 # weight favorite songs more heavily than others weight=yes # default history filename history=~/.randomplay_history # default player programs player[mp3]=mpg321 player[ogg]=ogg123 # player[wav]=play-sample # player[flac]=mplayer # player[avi]=mplayer # etc.. # default "new song announce" program announce = osd_cat -p bottom -l 2 -A right -c white -d 5 -f '-etl-fixed-medium-r-*-*-24-*-*-*-*-*-*-*' # define keystrokes -- multiple characters means all those characters will work # like - the key to increase a song's random weighting key[like]=+ # dislike - the key to decrease a song's random weighting key[dislike]=- # reset - the key to reset a song's random weighting to 0 key[reset]=0= # next - the key to advance to the next song key[next]=NnFf # back - the key to go to the previous song key[back]=Bb # quit - the key to quit immediately key[quit]=Qq # last - the key to quit after the current song is done key[last]=Ll # help - the key to display define keystrokes key[help]=Hh? randomplay-0.60+nmu1/changelog.gz0000644000000000000000000000465210514465747013664 0ustar k2EchangelogXsvRKФ(YTqIdlCo۽HjGboo}gŋlȖBBw{Ո_7B4s/ 'w /6rYQVXDȝ(u+.u.ɒCpw*V [ * (dP9mr'j^gG/ur5Jtt霃i-# QZzaK:浩Ĵ>pT4z_B<;s$$b֡?{vFEYX坾aYAxv KlKN}ubFl|. S]|*+֦$tVwj$j $[SŔl\U@Vp~:z{-.F/FJWusv+ (+"ocGUεҘE\fg'sS }YJV7dՄB"aMSt }-'ٱP+ɐsueAt 'ӧwKa+dg|S`bh]QX3 hx:4f(4zņN4 %Z]^\ _ g75&on?#&A?:_HWs@sp۶<7h""G(ե&:M4EZ$|T~&xV̡P-k\HKHPCg+'ۙX`:5Ht?,!ﶫq99%PZ哂z=#3dilorEb{3Y_+ix+Kԏe&f)#g1ϱ& ƈ wNz55f<}U0Jܓ.QK5\䧎gMaO>ȜʭIn[\x 5!ʒC V*8㳾}Sh]{;9MOrpqbp~=Tt*ѵ4REɛ_N9ZiH_|9y/('>V}W+8vol^nME*&)Dy"|(ًWTgGxUlZUf9?1)JPh9WkFgm-M<4s`~Xy* Kud]TNPn!(z NJ;PK=W[bGcc|SFZ7 Ĩ 澘Ie.uR$3-(qTAt0U q ;6naiBZ"D ᅋx.$la<[\RAb٨;֍;c፮aA[kۥ`^h{7lxH4|&l%wz^&z*LThX@l^|8^AкA\w5 șRϊ2~xgz8$\Jq2x1^'/b AId$#ʽf~ LTt1R;Ӏ=^܇?\"7oz&>H0J}2jFm Q2߶'}ٵRmHEu,AGGi0GMxBoh]G/Q\#-e߃sD ucePq5vo\ؿH6a ZY 3@̈́ן3Hpy~F0/_\mvQ B6߃L- n\ƍ,/&E.\'Kg) HT}xw>&|ov+ϸ9randomplay-0.60+nmu1/randomplay0000755000000000000000000007554311740642266013471 0ustar #!/usr/bin/perl use strict; use File::Find; use File::stat; use Getopt::Long; use Date::Calc qw/Today Delta_Days/; use Date::Manip qw/UnixDate/; use Pod::Usage; use Term::ReadKey; use POSIX ":sys_wait_h"; use MP3::Info; use Ogg::Vorbis::Header::PurePerl; use Time::localtime; $| = 1; # make sure we put the terminal back in a good state no matter how we quit END { ReadMode 1; }; my $version = "v0.60"; my %player; my $extensions; my @played; my $played_position=0; my @files; my $help = 0; my $man = 0; my $random = 1; my $verbose = 0; my $weight_favorites = 1; my $quiet = 0; my $days = 10; my $delay = '1'; my $history_file = $ENV{"HOME"} . "/.randomplay_history"; my @search_path; my %history; my %favorite; my $regexp = ""; my $regexp_if_statement; my $names = 0; my $tracks = -1; my $remember = 1; my $useutf8 = 1; my $maxsize = 0; my $maxtime = 0; my $zero = 0; my $last_played = 0; my $basedir = ""; my $clear_history = 0; my $version_only = 0; my $announce_song = ""; my $newer_than = ""; my $older_than = ""; $announce_song = "osd_cat -p bottom -l 2 -A right -c white -d 5 -f '-etl-fixed-medium-r-*-*-24-*-*-*-*-*-*-*'" if ( -x "/usr/bin/osd_cat" || -x "/usr/local/bin/osd_cat" ); my %key; sub shuffle { my $array = shift; my $i; for ($i = @$array; --$i; ) { my $j = int rand ($i+1); next if $i == $j; @$array[$i,$j] = @$array[$j,$i]; } } sub PlayMusic { $_ = $File::Find::fullname; eval $regexp_if_statement; } # KillOrPausePlayer recursively signals the requested process ID and all of its descendents # This is a bit of a hack--there should be a better way to do this, but I haven't found one. # Normally, kill HUP => -$$ should do the job, except this doesn't work when randomplay itself # is called from a shell script and all output from child processes is redirected to null. # This subroutine assumes that shelling out to ps is going to work, which may not be totally # safe--at the very least, it reduces the portability of the script. # Any suggestions for a better way to do this are welcome. sub KillOrPausePlayer { my $pid = shift; my $sig = shift || "HUP"; local $SIG{$sig} = 'IGNORE'; # don't want to kill ourselves here--shouldn't happen, but just in case. if (open PS, "ps -o pid,ppid |") { eof PS; # Force ps to run now. Otherwise it would run at the first read operation. kill $sig => $pid; foreach my $line () { KillOrPausePlayer($1, $sig) if ($line =~ /^\s*(\d+)\s*(\d+)\s*$/ and ($2 eq $pid)); } close PS; } else { # if we can't read the process table, just try killing the child process group kill $sig => -$$; } } # check to make sure players are actually executable sub CheckPlayers { my %executable; $executable{$_} = 0 foreach (keys %player); foreach my $dir (split ':', $ENV{PATH}) { foreach (keys %executable) { $executable{$_} = 1 if ( -x $dir . '/' . $player{$_} ); } } foreach (keys %executable) { unless ($executable{$_}) { print "Error: can't find player for extension $_ ($player{$_}) in path! Will not play $_ files.\n"; delete $player{$_}; } } die "None of the specified players are available, quitting.\n" unless keys %player; } # read in settings from ~/.randomplayrc, if any; be as flexible as possible in interpreting lines if (open IN, $ENV{"HOME"} . "/.randomplayrc") { while () { /^\s*days[\s=]*(.*)$/i and $days = $1; /^\s*history[\s=]*(.*)$/i and $history_file = $1; /^\s*random/i and $random = 1; /^\s*norandom/i || /^\s*random[\s=]*(no|off|false)/i and $random = 0; /^\s*pause[\s=]*(.*)$/i and $delay = $1; if (my ($ext,$prog) = /^\s*player\s*[\{\[\(]([A-Za-z0-9]+)[\}\]\)][\s=]*(.*)$/i) { $ext =~ tr/A-Z/a-z/; $player{$ext} = $prog; } # this doesn't check to make sure the keystroke you are trying to define actually # corresponds to a real keystroke, but there's no harm to defining a nonexistent keystroke if (my ($func,$keystroke) = /^\s*keys?\s*[\{\[\(]([A-Za-z]+)[\}\]\)]\s*[\s=]\s*(.*)$/i) { $func =~ tr/A-Z/a-z/; $func =~ s/[^a-z]//g; $key{$func} = $keystroke; } /^\s*quiet/ and $quiet = 1; /^\s*noquiet/ || /^\s*quiet[\s=]*(no|off|false)/ and $quiet = 0; /^\s*verbose/ and $verbose = 1; /^\s*noverbose/ || /^\s*verbose[\s=]*(no|off|false)/ and $verbose = 0; /^\s*remember/ and $remember = 1; /^\s*noremember/i || /^\s*remember[\s=]*(no|off|false)/i and $remember = 0; /^\s*utf8/i and $useutf8 = 1; /^\s*noutf8/i || /^\s*utf8[\s=]*(no|off|false)$/i and $useutf8 = 0; /^\s*weight/i and $weight_favorites = 1; /^\s*noweight/i || /^\s*weight[\s=]*(no|off|false)/i and $weight_favorites = 0; /^\s*tracks[\s=]*(.*)$/i and $tracks = $1; /^\s*maxsize[\s=]*(.*)$/i and $maxsize = $1; /^\s*maxtime[\s=]*(.*)$/i and $maxtime = $1; /^\s*basedir[\s=]*(.*)$/i and $basedir = $1; /^\s*announce[\s=]*(.*)$/i and $announce_song = $1; /^\s*newer(?:[_-]than)?[\s=]*(.*)$/i and $newer_than = $1; /^\s*older(?:[_-]than)?[\s=]*(.*)$/i and $older_than = $1; /^\s*names$/i and $names = 1; } close IN; } # read in command-line options, if any; can override ~/.randomplayrc settings GetOptions( 'announce|a=s' => \$announce_song, 'days=s' => \$days, 'history=s' => \$history_file, 'random!' => \$random, 'help|?' => \$help, 'man' => \$man, 'pause=s' => \$delay, 'quiet!' => \$quiet, 'regexp|regex|r=s' => \$regexp, 'names-only|n' => \$names, 'tracks=i' => \$tracks, 'maxsize=s' => \$maxsize, 'maxtime=s' => \$maxtime, 'remember!' => \$remember, 'utf8!' => \$useutf8, 'last=i' => \$last_played, 'basedir=s' => \$basedir, 'clear' => \$clear_history, 'version' => \$version_only, 'verbose|v!' => \$verbose, 'weight|w!' => \$weight_favorites, 'player=s' => \%player, 'newer-than|newer_than|newer=s' => \$newer_than, 'older-than|older_than|older=s' => \$older_than, 'key=s' => \%key, 'keys=s' => \%key, '0' => \$zero ); $key{'like'} = "+" unless exists $key{'like'}; $key{'dislike'} = "-" unless exists $key{'dislike'}; $key{'next'} = "NnFf" unless exists $key{'next'}; $key{'back'} = "Bb" unless exists $key{'back'}; $key{'quit'} = "Qq" unless exists $key{'quit'}; $key{'last'} = "Ll" unless exists $key{'last'}; $key{'reset'} = "0=" unless exists $key{'reset'}; $key{'pause'} = "Pp" unless exists $key{'pause'}; $key{'help'} = "Hh?" unless exists $key{'help'}; # define default music file players if none defined unless (keys %player) { $player{'ogg'} = 'ogg123'; $player{'mp3'} = 'mpg321'; # $player{'wav'} = 'play-sample'; # actually, most people don't want to play WAVs, and this will cause randomplay not to work # with default configuration if you don't have sound-recorder installed (which is most people) } print "randomplay $version\n" unless ($quiet or $names); exit(0) if $version_only; pod2usage(1) if $help; pod2usage(-exitstatus => 0, -verbose => 2) if $man; &CheckPlayers; $delay =~ s/[^0-9]//g; $delay = '1' unless length $delay; $days = 0 if ($zero or $last_played); # -0 is shortcut for --days=0, assume -0 when we ask for last x played songs $remember = 0 if ($last_played); # --last implies no remember $random = 0 if ($last_played); # --last implies no shuffle $weight_favorites = 0 unless ($random); # if the user requests nonrandom list, then don't do preferential weighting # convert newer_than, older_than and days value into actual days for ($newer_than, $older_than, $days) { # if date is in date (rather than delta format), calculate the delta if (m{/}) { my $x = localtime(UnixDate($_,"%s")); my $y = Delta_Days($x->year()+1900,$x->mon()+1,$x->mday(),Today()); $_ = $y if $y; } else { # otherwise, convert d, m, w, y to absolute days s/days?/d/gi; s/weeks?/w/gi; s/months?/m/gi; s/years?/y/gi; s/[^0-9dwmy]//gi; if (/y/i) { s/[^0-9]//g; $_ = $_ * 365; } elsif (/m/i) { s/[^0-9]//g; $_ = $_ * 31; } elsif (m/w/i) { s/[^0-9]//g; $_ = $_ * 7; } else { s/[^0-9]//g; } } } $extensions = '\.' . join('$|',keys(%player)) . '$'; $extensions =~ s/\|/\|\\./g; $basedir =~ s/~/$ENV{"HOME"}/; # expand tilde in basedir $basedir =~ s{([^/])$}{$1/}g if $basedir; # add a trailing slash to basedir if not specified print "Warning: base directory $basedir does not exist or is not readable!\n" if (not $quiet and $basedir and not -d $basedir); if ($regexp) { $regexp_if_statement = "if (/$extensions/i && /" . join ('/i && /', split / /, $regexp) . '/i) {'; } else { $regexp_if_statement = "if (/$extensions/i) {"; } # this seems to be a very slow way to check for file dates, but it's a first stab. # more efficient suggestions welcome. $regexp_if_statement .= ' my $date_string = localtime(stat($_)->mtime); if ((! $newer_than) || (Delta_Days($date_string->year()+1900,$date_string->mon()+1,$date_string->mday(),Today())) < $newer_than ) { if ((! $older_than) || (Delta_Days($date_string->year()+1900,$date_string->mon()+1,$date_string->mday(),Today())) > $older_than ) { if (defined $history{$_} and $days) { if (Delta_Days(@{$history{$_}},Today()) > $days) { push @files, $_; } } else { push @files, $_; } } } } '; if ($maxsize) { # convert maxsize into raw bytes $_ = $maxsize; s/[^0-9]//g; if ($maxsize =~ /m$/i) { $maxsize = $_ * 1048576; } elsif ($maxsize =~ /k$/i) { $maxsize = $_ * 1024; } else { $maxsize = $_; } } if ($maxtime) { # convert maxtime into raw seconds $_ = $maxtime; s/[^0-9]//g; if ($maxtime =~ /m$/i) { $maxtime = $_ * 60; } elsif ($maxtime =~ /h$/i) { $maxtime = $_ * 3600; } elsif ($maxtime =~ /d$/i) { $maxtime = $_ * 86400; } else { $maxtime = $_; } } $history_file =~ s/~/$ENV{"HOME"}/; unlink $history_file if ($clear_history and -e $history_file); if (open IN, $history_file) { # read in history file my $line_count = 0; if ($last_played) { # need to count lines in history if there is a request for last x songs $line_count += tr/\n/\n/ while sysread(IN, $_, 2 ** 16); seek IN,0,0; } while () { # parse history file into filenames and last played dates if (my ($filename, $date, $favorite) = /^([^\t]*)\t([^\t]*)\t?(.*)?$/) { $history{$filename} = [ split "/", $date ]; $favorite{$filename} = $favorite if ($favorite ne ""); push @files, $filename if ($last_played and $line_count-- <= $last_played); } } close IN; } elsif (-e $history_file) { print STDERR "Warning: could not open history file $history_file, check file permissions.\n"; } my $files_specified = 0; foreach (@ARGV) { # parse filenames and directory names from the command line s/^~/$ENV{"HOME"}/; # expand ~ as home directory s/^=/$basedir/; # = directories are relative to the basedir if (-d $_) { push @search_path, $_; } elsif ($basedir and -d $basedir . $_) { push @search_path, $basedir . $_; } elsif (-f $_) { $files_specified = 1; $_ = $ENV{"PWD"} . "/" . $_ unless (/^\//); if (defined $history{$_} and $days) { if (Delta_Days(@{$history{$_}},Today()) > $days) { push @files, $_; } } else { push @files, $_; } } } push @search_path, ($basedir ? $basedir : ".") unless (@search_path or @files or $last_played or $files_specified); find( { follow => 1, follow_skip => 2, wanted => \&PlayMusic }, @search_path) if (@search_path and not $last_played); shuffle(\@files) if ($random and scalar(@files) > 2); @files = sort(@files) unless $random; # put in alphabetical order if nonrandom requested my $bytes_played = 0; my $start_time = time; my $index = 0; my $any_left = 1; my $announce_pid = 0; MP3::Info::use_mp3_utf8($useutf8); # enable/disable UTF-8 printing for MP3 tags my $next_song; while (@files or $played_position < scalar @played) { if ($played_position < scalar @played) { $next_song = $played[$played_position]; } else { last unless $tracks--; # finished playing specified tracks, stop playing last if $maxtime and time - $start_time > $maxtime; # finished playing specified number of seconds, stop playing if ($weight_favorites) { # random weighting, we flip a coin but make it easier or harder to $next_song = ""; # get "heads" (rand(1)>0.5) based on the song's score until ($next_song) { # score = 5 means always play my $rand = rand(1); # score = -5 means never play unless there are (almost) no other songs left if ($rand > (0.5 - (exists $favorite{$files[$index]} ? $favorite{$files[$index]} : 0) * 0.1)) { $any_left = 1; # if we found a song, that proves that there are some less with $next_song = $files[$index]; # preference > -5 splice @files, $index, 1; $index = @files-1 if ($index >= @files); } else { if (++$index >= @files) { unless ($any_left) { # if we've been all the way through and haven't picked any songs $next_song = shift @files; # grab the next one in the list } $index = 0; $any_left = 0; } } } } else { # otherwise just play the next song in the list $next_song = shift @files; } } if ($maxsize) { # finished playing specified number of bytes $bytes_played += stat($next_song)->size; last if $bytes_played > $maxsize; } $next_song =~ s/`/\\`/gi; # escape backticks to avoid shenanigans with filenames (could this even happen? Probably not unless/until we have a prerecorded index file.) my $song_title = ""; my $song_artist = ""; if ($names) { print $next_song . "\n"; } else { # grab whatever mp3/ogg tags we can read my $song_info = ""; # at some point we might want to be more selective if ($next_song =~ /mp3$/i) { # and smarter with presentation and support other tags my $tag = get_mp3tag($next_song); while (my ($k, $v) = each %{$tag}) { $song_info .= $k . ": " . $v . "\n" if $v; $song_title = $v if ($k =~ /title/i); $song_artist = $v if ($k =~ /artist/i); } } elsif ($next_song =~ /ogg$/i) { my $tag = Ogg::Vorbis::Header::PurePerl->new($next_song); foreach ($tag->comment_tags) { $song_info .= $_ . ": " . join(" ", (defined $tag->comment($_) ? $tag->comment($_) : "")) . "\n"; $song_title = join(" ", (defined $tag->comment($_) ? $tag->comment($_) : "")) if (/title/i); $song_artist = join(" ", (defined $tag->comment($_) ? $tag->comment($_) : "")) if (/artist/i); } } KillOrPausePlayer $announce_pid if $announce_pid; if ($next_song and $announce_song) { unless ($announce_pid = fork()) { # we need to close STDIN so the announce program doesn't hold into it close STDIN; if ($song_artist and $song_title) { if (length ($song_title . $song_artist) < 40) { $song_title = "$song_artist / $song_title"; } else { $song_title = "$song_artist\n$song_title"; } } elsif ($song_artist) { $song_title = "$song_artist"; } elsif (not $song_title) { $song_title = $next_song; # remove path and extension for screen display $song_title =~ s{^.*/}{}; $song_title =~ s{\..*?$}{}; } if (open ANNOUNCE, "|$announce_song") { print ANNOUNCE $song_title; close ANNOUNCE; } exit; } } $song_info = "Playing ${next_song}...\n" unless $song_info; $song_info .= "Weight: " . ($favorite{$next_song} ? $favorite{$next_song} : 0) . "\n"; print "---------------------\n" . $song_info unless $quiet; my $pid; unless ($pid = fork()) { # we fork; either the child plays the whole song or is killed by us foreach my $ext (keys %player) { $next_song =~ m/$ext$/i and exec($player{$ext} . " \"" . $next_song . "\"" . ($verbose ? "" : " > /dev/null 2>&1")); } } # add the currently played song to the played history in case user decides to go back push @played, $next_song unless ($played_position < scalar @played); $played_position++; my $kid; ReadMode 3; # in case the music player changes the terminal mode, put it back in something where my $paused = 0; # start each song assuming we are *not* paused do { # we can still catch keystrokes; ReadMode 3 will allow user to suspend with ctrl-z, however my $keystroke = ReadKey(0.01); # $kid = waitpid(-1,WNOHANG) || 0; # print "waiting for $pid\n"; $kid = waitpid($pid,WNOHANG) || 0; # print "got $kid and $keystroke\n"; if (not defined $keystroke) { } elsif ($key{'pause'} =~ /\Q$keystroke\E/) { &KillOrPausePlayer($pid, ($paused) ? 'CONT' : 'STOP'); $paused = !$paused; print (($paused) ? "Paused... (hit " . substr($key{'pause'},0,1) . " to continue)\n" : "Continuing...\n") unless $quiet; } elsif ($key{'next'} =~ /\Q$keystroke\E/) { print "Skipping to next song...\n" unless $quiet; &KillOrPausePlayer($pid); } elsif ($key{'back'} =~ /\Q$keystroke\E/) { if ($played_position > 1) { print "Skipping to previous song...\n" unless $quiet; $played_position -= 2; &KillOrPausePlayer($pid); } else { print "Already on first song, can't go back...\n" unless $quiet; } } elsif ($key{'quit'} =~ /\Q$keystroke\E/) { print "Quitting...\n" unless $quiet; &KillOrPausePlayer($pid); last; } elsif ($key{'last'} =~ /\Q$keystroke\E/) { print "Quitting after this song...\n" unless $quiet; @files=qw//; # clear song playlist; at the end of this loop there will be no songs to play } elsif ($key{'like'} =~ /\Q$keystroke\E/) { $favorite{$next_song} = 5 if (++$favorite{$next_song} > 5); print "Increasing preference to " . $favorite{$next_song} . ".\n" unless $quiet; } elsif ($key{'dislike'} =~ /\Q$keystroke\E/) { $favorite{$next_song} = -5 if (--$favorite{$next_song} < -5); print "Decreasing preference to " . $favorite{$next_song} . ".\n" unless $quiet; } elsif ($key{'reset'} =~ /\Q$keystroke\E/) { $favorite{$next_song} = 0; print "Resetting preference to 0.\n" unless $quiet; } elsif ($key{'help'} =~ /\Q$keystroke\E/) { printf "%-15s %-30s\n", join(", ",split(//,$key{'help'})), "show keystrokes"; printf "%-15s %-30s\n", join(", ",split(//,$key{'like'})), "increase the currently playing song's random weight"; printf "%-15s %-30s\n", join(", ",split(//,$key{'dislike'})), "decrease the currently playing song's random weight"; printf "%-15s %-30s\n", join(", ",split(//,$key{'reset'})), "reset the currently playing song's randow weight to 0"; printf "%-15s %-30s\n", join(", ",split(//,$key{'next'})), "skip to the next song"; printf "%-15s %-30s\n", join(", ",split(//,$key{'back'})), "skip to the previous song"; printf "%-15s %-30s\n", join(", ",split(//,$key{'pause'})), "pause the playback (toggle)"; printf "%-15s %-30s\n", join(", ",split(//,$key{'last'})), "quit randomplay after the current song is finished"; printf "%-15s %-30s\n", join(", ",split(//,$key{'quit'})), "quit randomplay"; } } until ($kid > 0); ReadMode 1; # return terminal mode back to what the user probably wants, in case they cancel here sleep $delay if $delay; } next unless $remember; # don't record this track in history if we are in 'noremember' mode if (open OUT, ">>$history_file") { print OUT $next_song . "\t" . join('/',Today()) . (exists $favorite{$next_song} ? "\t" . $favorite{$next_song} : "" ) . "\n"; close OUT; } else { print STDERR "Warning: could not open history file $history_file for writing, check file permissions and disk space.\n"; } } __END__ =head1 NAME randomplay - command-line based shuffle music player that remembers songs between sessions =head1 SYNOPSIS randomplay [options] [directory ...] [file ...] Options: --help brief help message --man full man page --version show the version number and exit -q, --quiet quiet output: don't report name of each song as it is played -v, --verbose verbose output: include output from player program on console -d n, --days=n minimum days since track was last played for it to be considered (default 10 days) n can be number of days, or add suffix (W = week, M = month, Y= year), or can be an absolute date -0 special case to ignore history entirely (equivalent to --days=0) --random shuffle track listing (default random) --norandom --remember remember tracks played for future sessions (default remember) --noremember --utf8 use UTF-8 encoding for output (default utf8) --noutf8 -w, --weight weight random shuffle to prefer songs rated as favorites (default weight) --noweight -r, --regexp=regexp string only play files where filename matches regexp string -n, --names-only don't play tracks, only give list of filenames -t n, --tracks=n how many tracks to play (or display) total (default unlimited) --maxsize=number of bytes maximum number of bytes of music to play or display (default unlimited) --maxtime=duration how long to play before stopping (minutes or hours) (default unlimited) -l n, --last=n play last X songs in same order as they were played before --newer-than n, --newer n only play songs newer than n days old (e.g., W = week, M = month, Y = year, default days) or newer than date n --older-than n, --older n only play songs older than n days old or older than date n --basedir=directory directory to which all other directory specifications will be relative --history=filename file to use for randomplay history (default ~/.randomplay_history) --clear permanently clear (delete) the history file, start over with new history --pause=n delay between songs in seconds (default one second) --announce=command program to run to announce each new track track information is piped into program set to a blank string if you don't want any on screen announcements (default is xosd if it is installed) --player extension=command play files with extension with specified command (default mp3=mpg321, ogg=ogg123) --key function=keystroke define alternative keystrokes for player actions (defaults below) Default keys while playing: h, ? show keystrokes (set with --key help=keystrokes) + increase the currently playing song's random weight (set with --key like=keystrokes) - decrease the currently playing song's random weight (set with --key dislike=keystrokes) 0, = reset the currently playing song's randow weight to 0 (set with --key reset=keystrokes) f skip to the next song (set with --key next=keystrokes) b skip to the previous song (set with --key back=keystrokes) p pause the playback (toggle) (set with --key pause=keystrokes) l quit randomplay after the current song is finished (set with --key quit=keystrokes) q quit randomplay (set with --key last=keystrokes) =head1 DESCRIPTION randomplay is a shuffle music file player with a memory across sessions. randomplay also can remember which songs you like better and weight them more heavily in the random shuffle. It can also be used to play a directory or set of directories nonrandomly, remembering where you left off between seessions. You can specify any number of directories to be recursively searched for music files, as well as how long since a track was last played for it to re-enter the rotation. You can also keep track of different playlist histories (for example, for different users of the same account) by specifying a different history filename. All options can also be kept in ~/.randomplayrc, with one option per line, option name followed by a space followed by the option value. Command line settings will override settings specified in ~/.randomplayrc; later settings will override earlier settings. There is a default randomplayrc that comes with this package and includes all possible settings. =head1 EXAMPLE Play all ogg files in dir1 and dir2 under your home directory, and dir3 under the base directory specified in ~/.randomplayrc, which have not been played for 15 days in random order with 5 seconds between songs: randomplay --days=15 --pause=5 --player ogg=ogg123 ~/dir1 ~/dir2 =dir3 Play all ogg, wav, and mp3 files under the current directory (or base directory, if specified in .randomplayrc file) which have not been played for 10 days in alphabetical order, switch the 'skip to next song' keystroke to 'G' or 'g' and 'quit' to 'q' or 'c': randomplay --norandom --key next=Gg --key quit=qc Play all files under the current directory with the strings "frisell" and "bill" in the filename, in any order, (saves having to hunt down a file in a hierarchy), ignore whether the file has been played recently, but stop playing after 15 minutes: randomplay --regexp 'frisell bill' -0 --maxtime=15m Display 100M worth of music files, randomly sorted, without recording the history of tracks, using the default music directory (or the current directory if not specified): randomplay --maxsize=100M --noremember --names-only Play the last 10 songs played over again: randomplay --last=10 Play songs test.ogg, test2.ogg, test3.ogg, and all files in musicdir in random order without weighting preferred songs: randomplay --noweight test.ogg test2.ogg test3.ogg musicdir Copy 128M of songs into a Neuros Audio Player, using positron: positron add `randomplay --names-only --maxsize=128M` Pick a random jpeg or png file that has not been displayed in the last week from the 'images' directory and display it with ImageMagick 'display' command: randomplay --player jpg=display --player gif=display --days 7 ~/images =head1 NOTES Most options can be abbreviated as a single letter; e.g., -t for --tracks, -d for --days, -r for --regexp. You can also omit the "=" sign between the option and the value if you prefer. If many options start with the same letter (e.g., --maxtime, --maxsize), then you must use the full name for the option. For maxsize, you can use "k" for kilobytes and "m" for megabytes, or just enter the number of bytes. For maxtime, you can use "s" for seconds (default units), "m" for minutes, "h" for hours, or "d" for days. You might want to specify a "basedir" setting in your .randomplayrc, if all your music is under a certain directory. Then you can just give the relative names of the directories on the command line--e.g., you have "basedir=~/music" in ~/.randomplayrc, and rock is a directory under ~/music, so just enter "randomplay rock". Randomplay will search for music in specified subdirectories first under the current working directory, and then relative to the basedir setting, if any. If the specified directory name begins with =, it will be searched relative to the basedir setting, even if the specified directory also exists under the current working directory. Spaces in regexps are turned into "ands," making it more convenient to find the files you want. For example, --regexp 'thelonious monk blues' will play all songs with the words thelonious, monk, and blues in the filename, regardless of the order in which those words occur. Regexp searching is case-insensitive. If you specify an extension and a player for that extension, either in ~/.randomplayrc or on the command-line, only files with that extension will be played. You can, however, specify multiple players/extensions in either place, and all extensions listed will be played. The --last option implies --norandom and --noremember. =head1 COPYRIGHT Copyright (c) 2003-2006 Adam Rosi-Kessel. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law. =head1 BUGS Your history file will grow indefinitely. This could either be a bug or a feature. You may want to clear the history at some point. It might also compromise your privacy if other people can see your history file and you are concerned about people knowing which songs you have listened to. The system used to kill the music player when you decide to skip to the next track looks up the process IDs using 'ps'; this may not work on all platforms. The --maxtime option only works if you actually play the songs; it calculates actual time elapsed, rather than totalling the length of the tracks. This may or may not be the behavior you expect. =head1 AUTHOR Adam Rosi-Kessel, L =cut