diff options
author | Joey Hess <joeyh@debian.org> | 2011-01-03 09:49:13 -0800 |
---|---|---|
committer | Joey Hess <joeyh@debian.org> | 2011-01-03 09:49:13 -0800 |
commit | 44a3d2d1b9f97def795cbe8ab846d2a1593cfb64 (patch) | |
tree | 83d4ca187fc68a697952de2d599ccb4c4260e6e1 |
mpdtoys (0.24) unstable; urgency=low
* mprandomwalk: New toy to play random bits of all queued songs.
* mpinsert: Add -p option to begin playing inserted songs.
Closes: #608690
# imported from the archive
-rw-r--r-- | Makefile | 23 | ||||
-rw-r--r-- | MpdToys.pm | 62 | ||||
-rw-r--r-- | TODO | 4 | ||||
-rw-r--r-- | debian/changelog | 175 | ||||
-rw-r--r-- | debian/compat | 1 | ||||
-rw-r--r-- | debian/control | 24 | ||||
-rw-r--r-- | debian/copyright | 5 | ||||
-rw-r--r-- | debian/docs | 1 | ||||
-rwxr-xr-x | debian/rules | 7 | ||||
l--------- | mpcp | 1 | ||||
-rwxr-xr-x | mpfade | 118 | ||||
-rwxr-xr-x | mpgenplaylists | 84 | ||||
-rwxr-xr-x | mpinsert | 123 | ||||
-rwxr-xr-x | mplength | 44 | ||||
l--------- | mpload | 1 | ||||
l--------- | mpmv | 1 | ||||
-rwxr-xr-x | mprand | 38 | ||||
-rwxr-xr-x | mprandomwalk | 62 | ||||
-rwxr-xr-x | mprev | 56 | ||||
-rwxr-xr-x | mprompt | 370 | ||||
-rwxr-xr-x | mpskip | 90 | ||||
-rwxr-xr-x | mpstore | 272 | ||||
l--------- | mpswap | 1 | ||||
-rwxr-xr-x | mptoggle | 65 | ||||
-rwxr-xr-x | sats | 89 | ||||
-rwxr-xr-x | vipl | 161 |
26 files changed, 1878 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bd5148f --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +progs=mpstore mprev mpgenplaylists mpfade sats mpskip mptoggle mprand \ + mpinsert vipl mprompt mplength mprandomwalk + +PERLLIBDIR=$(shell perl -e 'use Config; print $$Config{installvendorlib}') + +build: + +install: + install -d $(DESTDIR)/usr/bin/ + install $(progs) $(DESTDIR)/usr/bin/ + + install -d $(DESTDIR)/usr/share/man/man1/ + for prog in $(progs); do \ + pod2man -c $$prog $$prog > $(DESTDIR)/usr/share/man/man1/$$prog.1; \ + done + + for link in mpload mpcp mpmv mpswap; do \ + ln -sf mpstore $(DESTDIR)/usr/bin/$$link; \ + ln -sf mpstore.1 $(DESTDIR)/usr/share/man/man1/$$link.1; \ + done + + install -d $(DESTDIR)/$(PERLLIBDIR) + install -m 0644 MpdToys.pm $(DESTDIR)/$(PERLLIBDIR) diff --git a/MpdToys.pm b/MpdToys.pm new file mode 100644 index 0000000..4290b80 --- /dev/null +++ b/MpdToys.pm @@ -0,0 +1,62 @@ +#!/usr/bin/perl +package MpdToys; + +sub findmatchingsongs { + my $term=shift; + my $mpd=shift; + + # pass urls through + if ($term=~/^(\w+):\/\//) { + return Audio::MPD::Common::Item->new(file => $term); + } + + my $coll=$mpd->collection; + + my $exactmatch=$coll->song($term); + if ($exactmatch) { + return $exactmatch; + } + + my @matches = ( + $coll->songs_from_album_partial($term), + $coll->songs_by_artist_partial($term), + $coll->songs_with_title_partial($term), + ); + + return dedupsongs(@matches); +} + +sub canmatch_fuzzy { + eval q{use String::Approx 'amatch'}; + return ! $@; +} + +sub findmatchingsongs_fuzzy { + my $term=shift; + my $mpd=shift; + + my $coll=$mpd->collection; + my @matches = ( + (map { $coll->songs_from_album($_) } + amatch($term, ['i'], $coll->all_albums())), + (map { $coll->songs_by_artist($_) } + amatch($term, ['i'], $coll->all_artists())) + ); + + # very slow, only try if nothing else matched + @matches = amatch($term, ['i'], $coll->all_songs()) + if ! @matches; + + return dedupsongs(@matches); +} + +sub dedupsongs { + my %seen; + my @ret; + foreach my $song (@_) { + push @ret, $song unless $seen{$song->file}++; + } + return @ret; +} + +1 @@ -0,0 +1,4 @@ +mprm: delete the currently playing file, once it finishes playing +sats: If mpd has one song in the playlist, and has it on repeat, sats will + never stop it playing, since it waits for mpd to move to the next + song. diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..fe3573c --- /dev/null +++ b/debian/changelog @@ -0,0 +1,175 @@ +mpdtoys (0.24) unstable; urgency=low + + * mprandomwalk: New toy to play random bits of all queued songs. + * mpinsert: Add -p option to begin playing inserted songs. + Closes: #608690 + + -- Joey Hess <joeyh@debian.org> Mon, 03 Jan 2011 13:49:13 -0400 + +mpdtoys (0.23) unstable; urgency=low + + * mplength: New toy to print length of current playlist. + * Updated to support change to how conntype is specified in + Audio::MPD 1.092950 + + -- Joey Hess <joeyh@debian.org> Sat, 06 Mar 2010 22:23:07 -0500 + +mpdtoys (0.22) unstable; urgency=low + + * mpswap, mpstore, etc: avoid crash when playing song time is not available. + Closes: 559182 + + -- Joey Hess <joeyh@debian.org> Wed, 02 Dec 2009 23:38:43 -0500 + +mpdtoys (0.21) unstable; urgency=low + + * mpinsert: Fix another utf-8 encoding problem. Closes: #549986 + * mpinsert: Avoid printing uninitialized value warning when + it fails to add any songs and -n is used. + + -- Joey Hess <joeyh@debian.org> Wed, 25 Nov 2009 06:43:25 -0500 + +mpdtoys (0.20) unstable; urgency=low + + * Work around bug #548305. + + -- Joey Hess <joeyh@debian.org> Sun, 27 Sep 2009 22:14:06 -0400 + +mpdtoys (0.19) unstable; urgency=low + + * Factored out matching code from mprompt and vipl. + * mprompt: Enable fuzzy matching by default, if the String::Approx module + is available. The -f flag is deprecated. + * vipl: Also enable fuzzy matching by default. + * vipl: Fix inserting/preservation of streaming urls in the playlist. + * mprompt, vipl: If the entered value exactly matches a file in the + mpd collection, use it without doing further matching. + * mpinsert: Enable partial/whole album/artist/fuzzy matching using + same code used for mprompt and vipl. + + -- Joey Hess <joeyh@debian.org> Tue, 22 Sep 2009 21:40:06 -0400 + +mpdtoys (0.18) unstable; urgency=low + + * mprompt: Configure getopt to not ignore case. + + -- Joey Hess <joeyh@debian.org> Wed, 03 Jun 2009 17:52:32 -0400 + +mpdtoys (0.17) unstable; urgency=low + + * mprompt: Rename terse switch to -T, -t was already taken. + + -- Joey Hess <joeyh@debian.org> Mon, 18 May 2009 18:31:48 -0400 + +mpdtoys (0.16) unstable; urgency=low + + * mpinsert: Don't insert multiple items in reverse order when + not playing. + * mpinsert: Only display number of first item added with -n. + + -- Joey Hess <joeyh@debian.org> Sat, 09 May 2009 20:23:17 -0400 + +mpdtoys (0.15) unstable; urgency=low + + * mprompt: Add -t switch, enabling a terse output mode where the output + is intended to be piped to a speech synth such as esound. + * mpinsert: Add -n switch, which prints the playlist position of + added items. This is useful if you want to insert an item and then jump + to it. + + -- Joey Hess <joeyh@debian.org> Sat, 09 May 2009 17:18:08 -0400 + +mpdtoys (0.14) unstable; urgency=low + + * mprompt: Add -f parameter that enables fuzzy matching of entered + search terms. (Needs String::Approx perl module.) + + -- Joey Hess <joeyh@debian.org> Sat, 11 Apr 2009 14:23:34 -0400 + +mpdtoys (0.13) unstable; urgency=low + + * mprompt: Add -t parameter, to allow clearing partially entered + lines after a timeout. + + -- Joey Hess <joeyh@debian.org> Thu, 09 Apr 2009 20:15:06 -0400 + +mpdtoys (0.12) unstable; urgency=low + + * mprompt: Support exit on EOF/ctrl-d. + * vipl, mprompt: Smarter searching, in particular: + - Remove duplicates from result list. + - Search for whole albums or artists before individual songs, + so that songs with the same name do not appear out of order. + + -- Joey Hess <joeyh@debian.org> Mon, 02 Mar 2009 18:43:55 -0500 + +mpdtoys (0.11) unstable; urgency=low + + * mprompt: prompt-based mpd client, designed for headless machines + + -- Joey Hess <joeyh@debian.org> Sat, 28 Feb 2009 19:35:18 -0500 + +mpdtoys (0.10) unstable; urgency=low + + * Fix some bad man page synopses. Closes: #515304 + + -- Joey Hess <joeyh@debian.org> Sun, 15 Feb 2009 13:35:39 -0500 + +mpdtoys (0.9) unstable; urgency=low + + * vipl: Fix handling of utf-8 in song info. Closes: #514119 + + -- Joey Hess <joeyh@debian.org> Wed, 04 Feb 2009 17:57:19 -0500 + +mpdtoys (0.8) unstable; urgency=low + + * vipl: New program, allows editing the mpd playlist in your text editor. + * Use debhelper v7; rules file minimisation. + * Use DESTDIR rather than DEST. + + -- Joey Hess <joeyh@debian.org> Wed, 30 Apr 2008 02:04:49 -0400 + +mpdtoys (0.7) unstable; urgency=low + + * sats: Add -n option allowing to stop after more than one song has played. + + -- Joey Hess <joeyh@debian.org> Wed, 30 Jan 2008 02:52:50 -0500 + +mpdtoys (0.6) unstable; urgency=low + + * mpswap: Fix handling when two hosts are specified on the command line. + + -- Joey Hess <joeyh@debian.org> Sat, 19 Jan 2008 17:28:02 -0500 + +mpdtoys (0.5) unstable; urgency=low + + * mpskip: Fix mispaste in synopsis. Closes: #457553 + + -- Joey Hess <joeyh@debian.org> Sun, 23 Dec 2007 11:19:50 -0500 + +mpdtoys (0.4) unstable; urgency=low + + * mptoggle: Allow the playlists to use to be specified at the command line. + + -- Joey Hess <joeyh@debian.org> Thu, 20 Dec 2007 15:39:06 -0500 + +mpdtoys (0.3) unstable; urgency=low + + * mpswap, mpcp, mpmv: Fix handling of only one host being specified on the + command line. + + -- Joey Hess <joeyh@debian.org> Sat, 15 Dec 2007 00:03:48 -0500 + +mpdtoys (0.2) unstable; urgency=low + + * mpgenplaylists: Sort, don't rely on find order. + * mprev: Don't crash if the playlist is empty. + * Add mpinsert. + + -- Joey Hess <joeyh@debian.org> Fri, 14 Dec 2007 23:39:54 -0500 + +mpdtoys (0.1) unstable; urgency=low + + * First release. + + -- Joey Hess <joeyh@debian.org> Sat, 01 Dec 2007 16:51:04 -0500 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..aa1008a --- /dev/null +++ b/debian/control @@ -0,0 +1,24 @@ +Source: mpdtoys +Section: sound +Priority: optional +Build-Depends: debhelper (>= 7), dpkg-dev (>= 1.9.0) +Maintainer: Joey Hess <joeyh@debian.org> +Standards-Version: 3.8.4 +Homepage: http://kitenet.net/~joey/code/mpdtoys/ +Vcs-Git: git://git.kitenet.net/mpdtoys + +Package: mpdtoys +Architecture: all +Depends: perl, libaudio-mpd-perl (>= 1.100430-1), ${misc:Depends} +Suggests: mpd, libproc-daemon-perl, libterm-readkey-perl, libstring-approx-perl +Description: small command line tools and toys for MPD + This is a collection of small toys and tools for doing various things + to MPD (Music Player Daemon) from the command line. Some of them are + very useful, while others are only amusing. + . + Some examples of things the mpdtoys can do include moving the playing + song between different mpd daemons on different machines, storing + the state of a mpd daemon and loading it back later, reversing the + playlist, slowly fading volume up or down, stopping playback after the + current song finishes, emulating a skipping record, and editing the + playlist in a text editor. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..24c49f0 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,5 @@ +Files: * +Copyright: (c) 2007 Joey Hess <joeyh@debian.org> +License: GPL-2+ + On Debian systems, the complete text of the GPL can be found in + /usr/share/common-licenses/GPL. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..1333ed7 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +TODO diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..6e8f2cc --- /dev/null +++ b/debian/rules @@ -0,0 +1,7 @@ +#!/usr/bin/make -f +%: + dh $@ + +# Not intended for use by anyone except the author. +announcedir: + @echo ${HOME}/src/joeywiki/code/mpdtoys/news @@ -0,0 +1 @@ +mpstore
\ No newline at end of file @@ -0,0 +1,118 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mpfade - fade mpd volume in or out + +=head1 SYNOPSIS + +mpfade [minutes] [max|min] [host] + +=head1 DESCRIPTION + +B<mpfade> behaves differently depending on whether mpd is playing or not. + +If mpd is not playing (or is paused), it starts it playing at a low volume, +and gradually cranks the volume up to the specified B<max> value (default +50) over the specified number of minutes (default 10). + +If mpd is already playing, it works in reverse, reducing the volume over +time until it's a the specified B<min> (defaults to a tenth of what it was +at the start). Then it stops playing. + +B<mpfade> tries to interact well with manual volume changes you make. If +the volume is fading up, and you change the volume manually, it stops. This +is intended to be useful when used as an alarm clock. If the volume is +fading down, changes you make to the volume will be noticed, and the fade +down will continue from that point. + +The B<minutes> value can be a floating point value. If the hostname is +omitted, the MPD_HOST environment variable will be used. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +my $seconds=60 * 10; +if (@ARGV && $ARGV[0] =~ /^[0-9.]+$/) { + $seconds=60 * shift; +} +my $endpoint; +if (@ARGV && $ARGV[0] =~ /^[0-9.]+$/) { + $endpoint=shift; +} + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} + +my $mpd=Audio::MPD->new(conntype => "reuse"); +my $vol=$mpd->status->volume; +if ($mpd->status->state eq 'play') { + if (defined $endpoint) { + if ($endpoint >= $vol) { + die "error: min ($endpoint) is not less than current volume ($vol)\n"; + } + } + else { + $endpoint=int($vol / 10); + } + print "fading down from $vol to $endpoint over $seconds seconds\n"; + fade($endpoint, $seconds); + $mpd->stop; +} +else { + if (! defined $endpoint) { + $endpoint=50; + } + $mpd->volume($vol=0); + print "fading up from $vol to $endpoint over $seconds seconds\n"; + $mpd->play; + fade($endpoint, $seconds); +} + +sub fade { + my $endpoint=shift; + my $seconds=shift; + + if ($seconds < 1) { + $mpd->volume($endpoint); + return; + } + + my $span=$endpoint - $vol; + my $oldvol=$vol; + my $curvol; + + # TODO calculate how long to optimally sleep + + while (sleep 1) { + $curvol=$mpd->status->volume; + if ($curvol != $oldvol) { + if ($span > 0) { + print "manual volume change, aborting fade up\n"; + return; + } + else { + print "manual volume change\n"; + $vol=$curvol; + } + } + + $vol=$vol + ($span / $seconds); + last if abs($vol - $endpoint) <= 1; + if (abs(int($vol - $oldvol)) > 1) { + $mpd->volume(int($vol)); + $oldvol=int($vol); + } + } +} diff --git a/mpgenplaylists b/mpgenplaylists new file mode 100755 index 0000000..881bbec --- /dev/null +++ b/mpgenplaylists @@ -0,0 +1,84 @@ +#!/bin/sh +set -e + +conf=/etc/mpd.conf +if [ -e $HOME/.mpdconf ]; then + conf=$HOME/.mpdconf +fi +if [ ! -e $conf ]; then + echo "error: $conf does not exist" >&2 + exit 1 +fi + +music_directory=$(grep "^music_directory" $conf | cut -d '"' -f 2 | sed -e "s!^\~!$HOME!") +playlist_directory=$(grep "^playlist_directory" $conf | cut -d '"' -f 2 | sed -e "s!^\~!$HOME!") + +if [ -z "$music_directory" ] || [ -z "$playlist_directory" ]; then + echo "error: failed to parse $conf" >&2 + exit 1 +fi + +if [ ! -d "$music_directory" ] || [ ! -d "$playlist_directory" ]; then + echo "error: both $music_directory and $playlist_directory need to exist" >&2 + exit 1 +fi + +rm $playlist_directory/\ *.m3u 2>/dev/null || true +IFS=" +" +for dir in $(cd "$music_directory"; find -type d -follow | sed 's!^./!!'); do + playlist="$(echo "$dir" | tr "_" " " | sed 's!/! - !g')" + if [ "$playlist" = . ]; then + playlist=all + fi + find "$music_directory/$dir" -type f -follow | sort \ + > "$playlist_directory/ $playlist".m3u +done + +exit + +<<POD + +=head1 NAME + +mpgenplaylists - generate mpd playlists for each subdirectory of music + +=head1 SYNOPSIS + +mpgenplaylists + +=head1 DESCRIPTION + +B<mpgenplaylists> generates mpd playlists. + +It reads your ~/.mpdconf or /etc/mpd.conf to figure out where mpd keeps its +music directory and playlist directory. + +For each subdirectory of the music directory, a playlist is generated in the +playlist directory. The playlists created by this tool always start with a +space to avoid conflicts with your manually created playlists. + +So if you keep your sound in Artist/Album/ directories, you'll get playlists +named like " Artist - Album", and also playlists named just " Artist" that +contain all music by that artist. An " all" playlist is also created, that +contains all your music. + +Each time it's run it updates the playlists, and removes any obsolete ones +that it created before. + +=head1 LIMITATIONS + +It does not currently sort songs in an album by track number, but instead +sorts by filename. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +POD diff --git a/mpinsert b/mpinsert new file mode 100755 index 0000000..db25c84 --- /dev/null +++ b/mpinsert @@ -0,0 +1,123 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; +use MpdToys; +use Encode; + +=head1 NAME + +mpinsert - insert song after currently playing song + +=head1 SYNOPSIS + +mpinsert [-n] [song ...] + +=head1 DESCRIPTION + +B<mpinsert> inserts a song (or songs) into the playlist directly after +the currently playing song. + +If no songs are specified on the command line, it will read a list from +stdin. + +Songs may be specified the same as they would be to mpc add: As paths to +files in the music database, or urls to stream. + +You can also enter the name of a playlist, or part of the name of an +album, artist, or song. Matching items will be added to the playlist. + +(If the perl String::Approx module is available, it will be used to handle +typos, etc in the names you enter.) + +=head1 OPTIONS + +=over 4 + +=item -n + +Print the playlist position number that the first song was inserted at. + +=back + +=item -p + +Play the song after inserting it. + +=back + +=head1 AUTHOR + +Copyright 2007-2009 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +use Getopt::Long; +my $shownum=0; +my $playnow=0; +GetOptions( + "n" => \$shownum, + "p" => \$playnow, +) || usage(); + +sub usage { + die "Usage: mpinsert [-n] [song ...]\n"; +} + +my @list=@ARGV; +if (! @list) { + while (<>) { + chomp; + push @list, $_; + } +} +@list=map { decode_utf8($_) } @list; + +my $mpd=Audio::MPD->new(conntype => "reuse"); +my $pl=$mpd->playlist; + +@list=reverse @list if $mpd->current; + +my @out; +my $firstsong; +foreach my $item (@list) { + if (! length $item) { + die "no item specified to insert\n"; + } + + my @matches=MpdToys::findmatchingsongs($item, $mpd); + if (! @matches && MpdToys::canmatch_fuzzy()) { + @matches=MpdToys::findmatchingsongs_fuzzy($item, $mpd); + } + + foreach my $song (@matches) { + $pl->add($song->file); + my @items=$pl->as_items; + if (! @items) { + die "failed!"; + } + my $pos=$#items; + + # move from end to just after current + my $current=$mpd->current; + if ($current) { + $pos=$current->pos+1; + $pl->move($#items, $pos); + } + + $firstsong=$pos unless defined $firstsong; + push @out, ($pos+1) if $shownum; + } +} + +if ($shownum) { + @out=reverse @out if $mpd->current; + print $out[0]."\n" if @out; +} +if ($playnow && defined $firstsong) { + $mpd->play($firstsong); +} diff --git a/mplength b/mplength new file mode 100755 index 0000000..621891b --- /dev/null +++ b/mplength @@ -0,0 +1,44 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mplength - calculates length of mpd playlist + +=head1 SYNOPSIS + +mplength [host] + +=head1 DESCRIPTION + +B<mplength> calculates the total length of the currently selected +playlist in mpd. The length is output to standard output in mm:ss format. + +=head1 SEE ALSO + +mpd(1) + +=head1 AUTHOR + +Copyright 2009 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} +my $mpd=Audio::MPD->new(conntype => "reuse"); +my $pl=$mpd->playlist; +my @list=$pl->as_items; +my $secs=0; +foreach my $item (@list) { + $secs+=$item->time; +} +my $mins=int($secs/60); +printf "%i:%02i\n", $mins, ($secs-$mins*60); @@ -0,0 +1 @@ +mpstore
\ No newline at end of file @@ -0,0 +1 @@ +mpstore
\ No newline at end of file @@ -0,0 +1,38 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mprand - play a random playlist + +=head1 SYNOPSIS + +mprand[host] + +=head1 DESCRIPTION + +B<mprand> picks a playlist at random and tells mpd to play it. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} +my $mpd=Audio::MPD->new(conntype => "reuse"); + +$mpd->playlist->clear; +my @playlists=$mpd->collection->all_playlists; +$mpd->playlist->load($playlists[rand @playlists]); +$mpd->play; diff --git a/mprandomwalk b/mprandomwalk new file mode 100755 index 0000000..88fe5e0 --- /dev/null +++ b/mprandomwalk @@ -0,0 +1,62 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mprandomwalk - play random bits of all queued songs + +=head1 SYNOPSIS + +mprandomwalk [maxduration] [host] + +=head1 DESCRIPTION + +B<mprandomwalk> plays random segments of randomly chosen songs in the +playlist. + +The B<maxduration> can control the maximum number of seconds of a song to +play at a time. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 AUTHOR + +Copyright 2010 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +my $maxduration=10; +if (@ARGV && $ARGV[0] =~ /^[0-9.]+$/) { + $maxduration=shift; +} + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} + +my $mpd=Audio::MPD->new(conntype => "reuse"); + +$mpd->play; + +my $pl=$mpd->playlist; +my @list=$pl->as_items; + +if (! @list) { + die "error: no songs queued\n"; +} + +while (1) { + my $song=$list[rand($#list)]; + my $length=$song->time; + next unless $length; + my $duration=rand($maxduration); + my $pos=rand($length-$duration); + $mpd->seekid($pos, $song->id); + sleep $duration; +} @@ -0,0 +1,56 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mprev - reverse the mpd playlist + +=head1 SYNOPSIS + +mprev [host] + +=head1 DESCRIPTION + +B<mprev> reverses mpd's playlist. That's all. The currently playing +song doesn't change. The song you heard last will be the next song to +play. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} + +my $mpd=Audio::MPD->new(conntype => "reuse"); +my $pl=$mpd->playlist; +my $current=$mpd->current; +if (! $current) { + die "playlist seems to be empty\n"; +} +my $pos=$mpd->current->pos; +my $id=$mpd->current->id; +my $past=0; +my @list=$pl->as_items; +foreach my $song (reverse @list) { + if ($past) { + $pl->moveid($song->id, $#list); + } + elsif ($id eq $song->id) { + $past=1; + } + else { + $pl->moveid($song->id, $pos++); + } +} @@ -0,0 +1,370 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; +use MpdToys; +use Getopt::Long; +use Term::ReadKey; +use Encode; + +=head1 NAME + +mprompt - simple prompt-based control for mpd + +=head1 SYNOPSIS + +mpompt [-s] [-m key=key] [-t n] [-f] [tty] [-T] [host] + +=cut + +sub usage { + die "Usage: mprompt [-s] [-m key=key] [-t n] [-f] [-t] [tty] [host]\n"; +} + +=head1 DESCRIPTION + +B<mprompt> is a mpd client with a prompt-based interface. It is +designed to be usable on a headless machine. + +At the prompt, enter the name of a playlist, or part of the name of an +album, artist, or song. Matching items will start playing. You can also +paste in urls to stream. + +(If the perl String::Approx module is available, it will be used to handle +typos, etc in the names you enter.) + +Use the left and right arrow keys to adjust volume, and the up and down +arrow keys to move through the playlist. + +The Tab and Enter keys can both be used to pause and unpause playback. +(Enter toggles pause only if nothing has been entered at the prompt.) + +Example of how to run mprompt in /etc/inittab: + + 1:2345:respawn:/usr/bin/mprompt /dev/tty1 + +=head1 OPTIONS + +=over 4 + +=item -s + +This option allows shell commands to be typed in to mprompt, to be +run by whatever user it is running as. (Typically root if it is run from +/etc/inittab). + +To enter a shell command, type a "!", followed by the command to run, +followed by Enter. + +=item -m key=key + +This option allows remapping keys. Any key can be remapped to any other +key, which is useful to support keyboard with unusual key layouts, or +missing keys. + +For alphanumeric and punctuation keys, individual symbols can be remapped. +For example, "-m a=b" will turn each entered "a" into "b". + +For other keys, use the following names: + +=over 4 + +=item <return> + +=item <tab> + +=item <space> + +=item <up> + +=item <down> + +=item <left> + +=item <right> + +=item <backspace> + +=back + +For example, -m "n=<down>" will map the "n" key to the down arrow, causing +that key to change to the next track; -m "<space>=<return>" will make the space +bar act as a pause. + +It's possible to swap keys too. For example, -m "<down>=<up>" -m "<up>=<down>" + +A single key can also be bound to a series of keystrokes. For example, +-m "1=Mule Variations<return>" will cause the "1" key to play the "Mule +Variations" album, a nice choice. + +=item -t n + +Adds a timeout, a specified number of seconds after which the entry +on the command line will be cleared. Useful for headless systems, to avoid +cat-on-keyboard confusing your later commands. + +=item -T + +Enables terse output mode. This mode tries to avoid displaying excessive +or complex things, with the intent that mprompt's output can be piped into +a speech synthesiser, such as espeak. + +=back + +=head1 SEE ALSO + +vipl(1) mptoggle(1) mpd(1) + +=head1 AUTHOR + +Copyright 2009 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +my $tty; +my $shell=0; +my $timeout=0; +my $terse=0; +my %controlchars = GetControlChars; +my %keysyms = ( + "\n" => '<return>', + "\t" => '<tab>', + " " => '<space>', + "\e[A" => '<up>', + "\e[3~" => '<up>', # delete on some terminals, raw on others + "\e[B" => '<down>', + "\e[D" => '<left>', + "\e[C" => '<right>', + $controlchars{ERASE} => "<backspace>", +); +my %keymap; + +Getopt::Long::Configure("no_ignore_case"); +GetOptions( + "s" => \$shell, + "m=s" => sub { + my ($old, $new)=split(/=/, $_[1], 2); + $keymap{$old}=$new; + }, + "t=i" => \$timeout, + "f" => sub { print STDERR "the -f option is now enabled by default\n" }, + "T" => \$terse, +) || usage(); + +if (@ARGV) { + $tty=shift; + close STDIN; + close STDOUT; + open(STDIN, "<", $tty) || die "open $tty: $!"; + open(STDOUT, ">", $tty) || die "open $tty: $!"; +} +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} +my $mpd=Audio::MPD->new(conntype => "reuse"); + +sub quit { + ReadMode("restore"); + exit(0); +}; +$SIG{INT}=$SIG{TERM}=\&quit; +ReadMode("raw"); + +$|=1; + +my $line=""; +my $sequence; +my $laststroke=time; + +showprompt(); + +KEY: while (my $key = ReadKey(0)) { + if ($timeout) { + if (length $line && time - $laststroke > $timeout) { + $line=""; + print " <timeout>\n" unless $terse; + showprompt(); + } + $laststroke=time; + } + + if ($key eq $controlchars{INTERRUPT} || + $key eq $controlchars{EOF}) { + quit(); + } + + # Sequences are started with escape, and accumulated + # until a recognised sequence is seen, or until it becomes clear + # that it is not part of a recognised sequence. + if (defined $sequence) { + $sequence.=$key; + if (exists $keysyms{$sequence}) { + $key=$sequence; + $sequence=undef; + } + else { + foreach my $sym (keys %keysyms) { + if ($sym=~/^\Q$sequence\E/) { + next KEY; # unfinished sequence + } + } + $key=$sequence; + $sequence=undef; + } + } + + $key = $keysyms{$key} if exists $keysyms{$key}; + $key = $keymap{$key} if exists $keymap{$key}; + + # The key may be mapped to a multiple letter sequence. + while (length $key) { + if ($key=~s/^(<[^>]+>)//) { + handle($1); + } + elsif ($key=~s/(.)//) { + handle($1); + } + } +} + +sub handle { + my $key=shift; + + if ($key eq "\e") { + $sequence=$key; + } + elsif ($key eq '<return>') { + if ($shell && $line =~ /^\!(.*)/) { + print "\nrunning $1\n"; + system($1); + } + elsif (length $line && $line !~ /^\s*$/) { + queue($line); + } + else { + toggle(); + } + $line=""; + showprompt(); + } + elsif ($key eq '<backspace>') { + if (length $line) { + chop $line; + print "\b \b"; + } + } + elsif ($key eq '<space>') { + print " "; + $line.=" "; + } + elsif ($key eq '<tab>') { + toggle(); + showprompt(); + } + elsif ($key eq '<left>') { + adjustvolume(-5); + } + elsif ($key eq '<right>') { + adjustvolume(+5); + } + elsif ($key eq '<up>') { + $mpd->prev; + $mpd->play; + showplaying(); + showprompt(); + } + elsif ($key eq '<down>') { + $mpd->next; + $mpd->play; + showplaying(); + showprompt(); + } + else { + print "$key"; + $line.=$key; + } +} + +sub adjustvolume { + my $amount=shift; + my $vol=$mpd->status->volume; + + $vol+=$amount; + if ($vol > 100) { + $vol=100; + } + elsif ($vol < 0) { + $vol=0; + } + + if (! $terse) { + print "\nvolume: $vol%\n"; + } + $mpd->volume($vol); + showprompt(); +} + +sub showprompt { + print "> $line"; +} + +sub showplaying { + print "\n"; + my $song=$mpd->current; + if (! defined $song) { + print "nothing queued\n"; + } + else { + if (! $terse) { + print encode_utf8($song->as_string)."\n"; + } + } +} + +sub toggle { + $mpd->pause; + my $state=$mpd->status->state; + print "\n"; + print "$state\n" if ! $terse || $state ne "play"; +} + +sub queue { + my $line=shift; + + print "\n"; + + my $pl=$mpd->playlist; + + eval q{$pl->load($line)}; + if (! $@) { + $pl->clear; + $pl->load($line); + $mpd->play; + + print "added $line playlist"; + showplaying(); + return; + } + + my @matches=MpdToys::findmatchingsongs($line, $mpd); + if (! @matches && MpdToys::canmatch_fuzzy()) { + print "trying fuzzy match..\n"; + @matches=MpdToys::findmatchingsongs_fuzzy($line, $mpd); + } + if (@matches) { + $pl->clear; + foreach (@matches) { + $pl->add($_->file); + } + $mpd->play; + print "added ".int(@matches)." songs"; + showplaying(); + } + else { + print "no matches found for \"$line\"\n"; + } +} @@ -0,0 +1,90 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; +use Time::HiRes qw(usleep); + +=head1 NAME + +mpskip - emulate a skipping record + +=head1 SYNOPSIS + +mpskip [num_skips] [duration] [skippos] [host] + +=head1 DESCRIPTION + +B<mpskip> makes mpd emulate a skipping record. + +If B<num_skips> is not specified (or is 0), it will skip forever (or until +someone manually seeks past the "bad" part of the song, or changes songs). + +The B<duration> is how long the skip should be, in seconds. Floating point +values can be used. + +The B<skippos> is the number of seconds into the song to place the skip point. +Default is the current play position. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 LIMITATIONS + +Doesn't insert the pop you hear on a real record player when the needle +skips. + +If run against a remote host, it may not skip at exactly the same place +each time due to network issues. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +my $num_skips=0; +if (@ARGV && $ARGV[0] =~ /^[0-9]+$/) { + $num_skips=shift; +} + +my $duration=1; +if (@ARGV && $ARGV[0] =~ /^[0-9.]+$/) { + $duration=shift; +} + +my $skippos; +if (@ARGV && $ARGV[0] =~ /^[0-9]+$/) { + $skippos=shift; +} + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} + +my $mpd=Audio::MPD->new(conntype => "reuse"); +if ($mpd->status->state ne 'play') { + die "error: mpd has to be playing first\n"; +} + +my $songid=$mpd->current->id; +$skippos=$mpd->status->time->seconds_sofar unless defined $skippos; + +print "skipping".($num_skips ? " $num_skips times" : ""). + " at position $skippos for $duration seconds\n"; + +while (usleep $duration * 1000000 / 2 ) { + my $status=$mpd->status; + my $pos=$status->time->seconds_sofar; + exit if $status->state ne 'play'; + exit if $mpd->current->id ne $songid; + exit if $pos > $skippos + $duration + 1; + next if $pos <= $skippos; + $mpd->seek($skippos); + if ($num_skips) { + $num_skips--; + exit unless $num_skips; + } +} @@ -0,0 +1,272 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; +use Data::Dumper; + +=head1 NAME + +mpstore - store and transfer mpd state between daemons + +=head1 SYNOPSIS + +mpstore [host] > file + +mpload [host] < file + +mpcp [src] dest + +mpmv [src] dest + +mpswap [A] B + +=head1 DESCRIPTION + +These commands allow saving, loading, and transferring state between mpd +daemons running on different hosts. + +B<mpstore> dumps a daemon's state to stdout. + +B<mpload> loads a state dump from stdin and sends it to a daemon. + +B<mpcp> copies the state from the src daemon to the dest daemon, causing it +to begin to play the same song as the src daemon, at the same position. + +B<mpmv> moves the state, so the dest daemon is left playing what the src +daemon was playing, and the src daemon is paused. + +B<mpswap> exchanges the state of daemons A and B, swapping what they're +playing. + +The first hostname passed to each command can be omitted, if it is then +the MPD_HOST environment variable will be used. Like the MPD_HOST variable, +the hostname can be of the form "password@hostname" to specify a password. +If any hostname is "-", the MPD_HOST setting will be used. + +The full list of state that is handled is: + +=over + +=item the contents of the playlist + +=item the playback state (playing, paused, stopped) + +=item the currently playing song + +=item the position within the playing song + +=item the volume control + +=item the repeat, random, and cross fade settings + +=back + +=head1 LIMITATIONS + +The host that state is transferred to must have the playing song available +in its library, with the same filename. It's ok if some other songs in the +playlist are not available; such songs will be skipped. + +B<mpcp> cannot perfectly synchronise playback between the two daemons. +Network latency and timing prevent this. It should manage better than 0.5 +second accuracy. If you need better accuracy of synchronised playback, +you should probably use Pulse Audio. + +=head1 BUGS + +The file format is not the same that mpd uses for saving its own state, +which would be nice. + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +# Allow "-" to be specified as a host, it will use MPD_HOST then. +my $real_MPD_HOST=$ENV{MPD_HOST}; +my $prev_host; +sub sethost { + my $host=shift; + if ($host eq "-") { + $host=$real_MPD_HOST; + if (! defined $host) { + die "error: MPD_HOST is not set, cannot use '-'\n"; + } + } + if (defined $prev_host && $host eq $prev_host) { + die "error: src and dest hosts cannot be the same\n"; + } + + $ENV{MPD_HOST}=$prev_host=$host; +} + +if ($0=~/mpswap/) { + if (@ARGV == 2) { + sethost(shift); + } + if (! @ARGV) { + die "error: not enough hosts specified\n"; + } + my $a=Audio::MPD->new(conntype => "reuse"); + sethost(shift); + my $b=Audio::MPD->new(conntype => "reuse"); + + my $asnap=snapshot($a, 1); + my $bsnap=snapshot($b, 1); + transfer($asnap, $b); + transfer($bsnap, $a); +} +elsif ($0=~/mpstore/) { + if (@ARGV) { + sethost(shift); + } + + my $src=Audio::MPD->new(conntype => "reuse"); + + my $snap=snapshot($src, 0); + delete $snap->{src}; # don't dump this object + $Data::Dumper::Terse=1; + $Data::Dumper::Indent=1; + print Dumper($snap); +} +elsif ($0=~/mpload/) { + if (@ARGV == 2) { + sethost(shift); + } + + my $dest=Audio::MPD->new(conntype => "reuse"); + my $code; + { + local $/=undef; + $code=<>; + } + my $snap=eval $code; + if ($@ || ! ref $snap) { + die "error: failed to parse stdin ($@)\n"; + } + transfer($snap, $dest); +} +else { + if (@ARGV == 2) { + sethost(shift); + } + + if (! @ARGV) { + die "error: not enough hosts specified\n"; + } + my $src=Audio::MPD->new(conntype => "reuse"); + sethost(shift); + my $dest=Audio::MPD->new(conntype => "reuse"); + + my $snap=snapshot($src, 1); + transfer($snap, $dest); +} + +sub snapshot { + my $mpd=shift; + my $pause=shift; + + my $status=$mpd->status; + my $state=$status->state; + my $current=$mpd->current; + if ($pause && $state eq 'play') { + $mpd->pause; + } + + my @playlist; + foreach my $song ($mpd->playlist->as_items) { + push @playlist, $song->file; + } + + return { + src => $mpd, + state => $state, + repeat => $status->repeat, + volume => $status->volume, + pos => $status->time ? $status->time->seconds_sofar : 0, + xfade => $status->xfade, + current => defined $current ? $mpd->current->file : undef, + playlist => \@playlist, + }; +} + +sub transfer { + my $snap=shift; + my $dest=shift; + + if (ref $snap->{playlist} eq 'ARRAY') { + # Feed playlist to dest. + $dest->playlist->clear; + eval { + $dest->playlist->add(@{$snap->{playlist}}); + }; + if ($@) { + # Try doing it a song at a time, in case only some + # songs are available. + foreach my $song (@{$snap->{playlist}}) { + eval { + $dest->playlist->add($song); + }; + if ($@) { + print STDERR "warning: failed to add song to playlist ($song)\n"; + } + } + } + } + + # Set misc settings. + $dest->repeat($snap->{repeat}) if exists $snap->{repeat}; + $dest->random($snap->{random}) if exists $snap->{random}; + $dest->volume($snap->{volume}) if exists $snap->{volume}; + $dest->fade($snap->{xfade}) if exists $snap->{xfade}; + + # Figure out the id of the song to play on dest. + my $id; + if (exists $snap->{current} && defined $snap->{current}) { + foreach my $song ($dest->playlist->as_items) { + if ($song->file eq $snap->{current}) { + $id=$song->id; + } + } + if (! defined $id) { + print STDERR "error: cannot find currently playing song (". + $snap->{current}.") on dest playlist\n"; + return; + } + } + + # Seek and set play state. + $dest->seekid($snap->{pos}, $id) if exists $snap->{pos} && defined $id; + if (exists $snap->{state}) { + if ($snap->{state} eq 'play') { + if ($0 =~ /mpcp/) { + # mpd only provides second accuracy, so src + # and dest are probably a fraction of a second + # off. For a more accurate copy of the state, + # seek the *src* back to the start of the + # current second too. + # Of course, this isn't perfect, due to + # network latency, etc. + $snap->{src}->seek($snap->{pos}); + $snap->{src}->play; + $dest->play; + } + else { + $dest->play; + } + } + elsif ($snap->{state} eq 'pause') { + $dest->pause; + } + elsif ($snap->{state} eq 'stop') { + $dest->stop; + } + } + + return 1; +} @@ -0,0 +1 @@ +mpstore
\ No newline at end of file diff --git a/mptoggle b/mptoggle new file mode 100755 index 0000000..60888e2 --- /dev/null +++ b/mptoggle @@ -0,0 +1,65 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; + +=head1 NAME + +mptoggle - single button control for mpd + +=head1 SYNOPSIS + +mptoggle [host] [playlist ...] + +=head1 DESCRIPTION + +B<mptoggle> allows mpd to be controlled by a single physical button. It +was designed for a linksys nslu2 running mpd, for which the only available +physical control is a power button that can be remapped to run an arbitrary +program. + +B<mptoggle> toggles playback each time it's run. So press the button once +to start, and a second time to stop. Each time it starts playing. By +default it selects a new different playlist, at random, to play. If +playlist names are specified at the command line, it will choose between +those at random. + +Example of how to make the nslu2's power button run mptoggle, in +/etc/inittab: + + ca:12345:ctrlaltdel:/usr/bin/mptoggle localhost + +=head1 SEE ALSO + +mprompt(1) mpd(1) + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} +my $mpd=Audio::MPD->new(conntype => "reuse"); + +if ($mpd->status->state ne 'play') { + $mpd->playlist->clear; + my @playlists; + if (@ARGV) { + @playlists=@ARGV; + } + else { + @playlists=$mpd->collection->all_playlists; + } + $mpd->playlist->load($playlists[rand @playlists]); + $mpd->play; +} +else { + $mpd->pause; +} @@ -0,0 +1,89 @@ +#!/usr/bin/perl +use strict; +use warnings; +use Audio::MPD q{0.19.0}; +use Getopt::Long; + +=head1 NAME + +sats - mpd stop after this song + +=head1 SYNOPSIS + +sats [-d] [-n num] [host] + +=head1 DESCRIPTION + +B<sats> is an acronym for Stop After This Song. It will wait for playback +of the currently playing song to finish, and then tell mpd to stop playing. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 OPTIONS + +=over 4 + +=item -d + +Daemonize rather than waiting in the foreground for the song to stop playing. + +=item -n num + +Stop after B<num> songs (default is 1). + +=back + +=head1 AUTHOR + +Copyright 2007 Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL version 2 or higher. + +http://kitenet.net/~joey/code/mpdtoys + +=cut + +my $num=1; +my $daemon=0; +GetOptions( + "n=i" => \$num, + "d" => \$daemon, +) || usage(); + +sub usage { + die "Usage: sats [-d] [-n num] [host]\n"; +} + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} + +my $mpd=Audio::MPD->new(conntype => "reuse"); + +my $song=$mpd->current; +if ($mpd->status->state ne 'play') { + die "no song is currently playing\n"; +} + +if ($daemon) { + eval q{use Proc::Daemon}; + Proc::Daemon::Init(); + # daemonising closed the connection to mpd + $mpd=Audio::MPD->new(conntype => "reuse"); +} + +# Polling is evil, it could look at the seek position, and sleep until the +# end. But that would break if something seeked in the song.. + +while (sleep 1) { + last if $mpd->status->state ne 'play'; + my $current=$mpd->current; + if ($current->id != $song->id) { + $num--; + if ($num == 0) { + $mpd->stop; + exit; + } + $song=$current; + } +} @@ -0,0 +1,161 @@ +#!/usr/bin/perl + +use warnings; +use strict; +use File::Spec; +use File::Temp; +use Audio::MPD q{0.19.0}; +use MpdToys; +use Encode; + +=head1 NAME + +vipl - edit mpd playlist + +=head1 SYNOPSIS + +B<vipl> [host] + +=head1 DESCRIPTION + +vipl allows editing the mpd playlist using your text editor. The current +playlist will be brought up in the editor. Delete or rearrange songs as +desired using the editor. + +You can also enter the name of a playlist, or part of the name of an +album, artist, or song. Matching items will be added to the playlist. +Streaming urls can also be entered. + +(If the perl String::Approx module is available, it will be used to handle +typos, etc in the names you enter.) + +The currently playing song is marked with a ">" at the front. To change +which song is playing, just move the ">" to a different song. + +If the hostname is omitted, the MPD_HOST environment variable will be used. + +=head1 AUTHOR + +Copyright 2008 by Joey Hess <joey@kitenet.net> + +Licensed under the GNU GPL. + +=cut + +if (@ARGV) { + $ENV{MPD_HOST}=shift; +} +my $mpd=Audio::MPD->new(conntype => "reuse"); +my $current=$mpd->current; +my $pl=$mpd->playlist; +my %names=map { $_->as_string => $_ } $pl->as_items; +my @origlist=$pl->as_items; + +my $tmp=File::Temp->new(TEMPLATE => "plXXXXX", DIR => File::Spec->tmpdir); +open (OUT, ">".$tmp->filename) || die "$0: cannot create ".$tmp->filename.": $!\n"; +foreach my $song (@origlist) { + print OUT ">" if defined $current && $song->id eq $current->id; + print OUT encode_utf8($song->as_string); + print OUT "\n"; +} +close OUT || die "$0: cannot write ".$tmp->filename.": $!\n"; + +my @editor="vi"; +if (-x "/usr/bin/editor") { + @editor="/usr/bin/editor"; +} +if (exists $ENV{EDITOR}) { + @editor=split(' ', $ENV{EDITOR}); +} +if (exists $ENV{VISUAL}) { + @editor=split(' ', $ENV{VISUAL}); +} +my $ret=system(@editor, $tmp); +if ($ret != 0) { + die "@editor exited nonzero, aborting\n"; +} + +my $changed=0; +my @list; +open (IN, "<".$tmp->filename) || die "$0: cannot read ".$tmp->filename.": $!\n"; +my $playing; +my $num=0; +while (<IN>) { + chomp; + $_=decode_utf8($_); + + if (s/^>\s*//) { + $playing=$num; + } + + if (exists $names{$_}) { + push @list, $names{$_}; + if (! $changed && !($origlist[$#list] && + $names{$_}->file eq $origlist[$#list]->file)) { + $changed=1; + } + $num++; + } + else { + next if /^\s*$/; + + my @matches=MpdToys::findmatchingsongs($_, $mpd); + if (! @matches && MpdToys::canmatch_fuzzy()) { + @matches=MpdToys::findmatchingsongs_fuzzy($_, $mpd); + } + + if (! @matches) { + print STDERR "$0: no matches for \"$_\"\n"; + } + else { + foreach (@matches) { + push @list, $_; + } + $changed=1; + $num+=@matches; + } + } +} +close IN; +if ($#list != $#origlist) { + $changed=1; +} + +if (! $changed) { + # yay for optimisation! +} +elsif ($current && grep { $_->file eq $current->file } @list) { + # Avoid touching the currently playing song, while changing + # the playlist around it. + $pl->crop; + my $pos=0; + my $past=0; + foreach my $song (@list) { + if (! $past && $song->file eq $current->file) { + $past=1; + # move current song into right location + $pl->move(0, $pos); + } + else { + $pl->add($song->file); + } + $pos++; + } + $mpd->play if @list; +} +else { + $pl->clear; + foreach my $song (@list) { + $pl->add($song->file); + } +} + +if (defined $playing) { + if (! defined $mpd->status->song || $playing ne $mpd->status->song || + $mpd->status->state ne 'play') { + $mpd->play($playing); + } +} +else { + $mpd->play(0) if @list; +} |