Perl makes it easy to select a random file and pass it as an argument to another program. I use the following script every day to select random mp3 playlists for my xmms queue. Here are examples from the pod:
$ random_file.pl /path/to/mp3s -t mp3 -e 'xmms -e' # random song
$ random_file.pl /path/to/mp3s -t m3u -e 'xmms -e' # random playlist
$ random_file.pl /path/to/mp3s -d -e 'xmms -e' # random directory
One of the nice features of this program is that it caches recently selected entries, so you don't end up adding the same mp3s over and over again, for example.
You may run the program without any options for a brief overview of command line options, or run
perldoc FILENAME for details.
Comments are most welcome.
#!/usr/local/bin/perl -w
#
# $Id: random_file.pl,v 1.17 2004/05/10 17:33:50 zackse Exp $
#
# picks a random file or directory and passes it to another program
use strict;
use File::Find;
use Cache::FileCache;
use Getopt::Long;
use Pod::Usage;
my %opts;
$opts{dirs}= shift;
unless ( $opts{dirs} and -d $opts{dirs} ) {
warn "need a directory\n\n";
pod2usage(2);
}
GetOptions(
\%opts, 'type:s@', 'dironly', 'exec:s', 'ignore:s@', 'cache:s', 'hel
+p'
);
pod2usage(1) if $opts{help};
$opts{exec} ||= '/bin/echo'; # bleh
pod2usage(2) unless $opts{exec};
##
my $files= get_candidates( $opts{dirs} );
my $selected= select_random_file( \%opts );
die "No match\n" unless $selected;
exec( split( ' ' => $opts{exec} ), $selected )
or die "error running '$opts{exec} $selected': $!\n";
# should never get here
exit;
##
sub get_candidates {
my $path= shift || return [];
# stores all files in specied directory tree
my $dircache= Cache::FileCache->new({
namespace => $path,
default_expires_in => 86400, # 1 day
auto_purge_interval => 3600 * 5, # 5 hours
});
my $files= $dircache->get( $path );
# regenerate file list
unless( $files and @$files ) {
find( sub { push @$files => $File::Find::name }, $path );
$dircache->set( $opts{dirs} => $files );
}
return $files;
}
sub select_random_file {
my $opts= shift;
my( $exts, $ignores )= map generate_patterns( $_ ) => @$opts{qw/type
+ ignore/};
# stores selected file
my $cache= Cache::FileCache->new({
namespace => $opts->{cache} || 'random_file',
default_expires_in => 86400 * 4, # 4 days
auto_purge_interval => 3600 * 5, # 5 hours
});
my $file_count;
my $selected= '';
foreach( @$files ) {
# match directories only?
next if $opts->{dironly} and ! -d;
# matches ignore list?
next if $ignores && $_ =~ $ignores;
# matches extensions list?
next unless ( ! $exts || /(?:$exts)\z/o );
# already selected recently?
next if $cache->get( $_ );
# OK, candidate for selection
$selected= $_ if rand( ++$file_count ) < 1; # see perlfaq5
}
return unless $selected;
$cache->set( $selected => 1 );
return $selected;
}
sub generate_patterns {
return map {
my $pats= join '|' => map "\Q$_\E" => @{ $_ || [] };
$pats ? qr/$pats/ : undef
} @_;
}
__END__
=head1 NAME
random_file.pl - pick a random file in a directory, pass it as an argu
+ment to another program
=head1 SYNOPSIS
random_file.pl B<PATH> -exec B<PROGRAM> [ -type B<FILEMASK> ]
[ -ignore B<EXCLUSION> ] [ -c B<CACHE_NAME> ] [ -d ]
Options:
-c --cache NAMESPACE use NAMESPACE for cache
-d --dironly only match against directories
-e --exec PROGRAM pass selection as argument to PROGRAM
-h --help display this help text
-i --ignore PATTERN ignore files matching PATTERN
-t --type EXTENSION files must match EXTENSION
=head1 EXPLANATION
Perhaps you have a large directory of mp3s, but you've grown tired of
scanning through the file dialog from xmms. This program can select
a random song, or playlist, or directory, and enqueue it for you:
$ random_file.pl /path/to/mp3s -t mp3 -e 'xmms -e' # song
$ random_file.pl /path/to/mp3s -t m3u -e 'xmms -e' # playlist
$ random_file.pl /path/to/mp3s -d -e 'xmms -e' # directory
Or maybe you have a nice collection of humorous videos you've download
+ed
from the Internet, and you'd like to view one at random with mplayer:
$ random_file.pl /path/to/downloads -t mpg -t mpeg -e 'mplayer -idx
+'
=head1 OPTIONS
=over 4
=item B<-cache>
This option specifies the namespace for the cache used to save recentl
+y
selected items. The default is 'random_file'.
=item B<-dironly>
This option specifies that only directories should be matched.
=item B<-exec>
This is an external program to which the result will be passed as a
parameter.
=item B<-ignore>
This is a pattern matching files you wish to be excluded from the gene
+rated
list of candidates. You may specify this option multiple times.
=item B<-type>
This is a pattern matching files you want to match, which is anchored
+to
the end of the filenames examined. For example, to match all mp3 files
+,
you would use C<-type mp3>. You may specify this option multiple times
+.
If you do not supply this parameter, all files will be matched.
=back
=head1 MORE EXAMPLES
View a random image from the opera web browser's cache, ignoring
any matching 'doubleclick':
random_file.pl /home/zackse/.opera/cache4 -t jpe -i doubleclick \
-e /usr/bin/X11/xv
=head1 USEFUL SHELL FUNCTIONS
I use this program every day to add random entries to my xmms playlist
+,
and I have the following bash shell functions that make this easy:
randalbum ()
{
DIR=${1:-/usr/local/audio};
random_file.pl $DIR -t m3u -e 'xmms -e'
}
randmp3 ()
{
DIR=${1:-/usr/local/audio};
random_file.pl $DIR -t mp3 -e 'xmms -e'
}
randmp3dir ()
{
DIR=${1:-/usr/local/audio};
random_file.pl $DIR -d -e 'xmms -e'
}
You can supply an alternate directory as an argument to the function,
or just update the default (/usr/local/audio) above.
You may also find the following one-liner useful to dump the contents
of the current cache of recently selected files (replace the cache nam
+e
'random_file' as needed):
$ perl -MCache::FileCache -le \
'my $cache= Cache::FileCache->new({namespace, "random_file"});
print join "\n" => $cache->get_keys()' \
| sort
=head1 AUTHOR
Evan A. Zacks <e@zacks.org>
Copyright (c) 2004 Evan A. Zacks. All rights reserved.
This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.
=head1 SEE ALSO
File::Find(3), Cache::Cache(3), perl(1)
=cut