summaryrefslogtreecommitdiff
path: root/vipl
diff options
context:
space:
mode:
Diffstat (limited to 'vipl')
-rwxr-xr-xvipl161
1 files changed, 161 insertions, 0 deletions
diff --git a/vipl b/vipl
new file mode 100755
index 0000000..903265c
--- /dev/null
+++ b/vipl
@@ -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;
+}