X-Git-Url: http://git.draconx.ca/gitweb/mpdhacks.git/blobdiff_plain/cf96918d75b08d0767efff3ec886bfd931b480fe..4c471596ff0f8f1c9d1e64813c8f9a04ef515691:/mpdmenu.pl diff --git a/mpdmenu.pl b/mpdmenu.pl index d22193c..241ab8e 100755 --- a/mpdmenu.pl +++ b/mpdmenu.pl @@ -21,7 +21,7 @@ binmode(STDOUT, ":utf8"); use IO::Socket::INET6; use Getopt::Long qw(:config gnu_getopt); use Scalar::Util qw(reftype); -use List::Util qw(any); +use List::Util qw(any max); use FindBin; use constant { @@ -37,6 +37,7 @@ my $host = $ENV{MPD_HOST} // "localhost"; my $port = $ENV{MPD_PORT} // "6600"; my $sock; +my ($albumid, $artist, $title); my $menu; my $mode = "top"; @@ -276,20 +277,93 @@ sub top_track_album { my ($entry) = @_; my @submenu; - my @submenu = make_submenu("$menu-TopAlbum", - "--artist=$entry->{Artist}", - "--album=$entry->{Album}"); + my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_ALBUMID"); + @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid") if $mbid; fvwm_cmd("AddToMenu", $menu, fvwm_label_escape("Album:\t$entry->{Album}"), @submenu); } -# Global hash for tracking what is to be "accepted". -my %accept = (); +# Given a release MBID, return a hash reference containing all its +# associated tracks in the MPD database. The hash keys are filenames. +sub get_tracks_by_release_mbid { + my ($mbid) = @_; + my %matches; + my $entry; + + return \%matches unless ($mbid); + mpd_exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")"); + while (<$sock>) { + last if (/^OK/); + die($_) if (/^ACK/); + + if (/^(\w+): (.*)$/) { + if ($1 eq "file") { + if (exists($matches{$2})) { + $entry = $matches{$2}; + } else { + $entry = {}; + $matches{$2} = $entry; + } + } + + add_track_metadata($entry, $1, $2); + } + } + + return \%matches; +} + +# Given a filename, return the IDs (if any) for that file in the +# current MPD play queue. +sub get_ids_by_filename { + my ($file) = @_; + my @results = (); + + mpd_exec("playlistfind", "file", $file); + while (<$sock>) { + last if (/^OK/); + die($_) if (/^ACK/); + + if (/^(\w+): (.*)$/) { + push @results, $2 if ($1 eq "Id"); + } + } + + return @results; +} + +# albumsort(matches, a, b) +# +# Sort hash keys (a, b) by disc/track number for album menus. +sub albumsort { + my ($matches, $a, $b) = @_; + + return $matches->{$a}->{Disc} <=> $matches->{$b}->{Disc} + || $matches->{$a}->{Track} <=> $matches->{$b}->{Track} + || $a cmp $b; +} -# Default values for stuff. -my ($album, $artist, $title); +# datesort(matches, a, b) +# +# Sort hash keys (a, b) by release date +sub datesort { + my ($matches, $a, $b) = @_; + + return $matches->{$a}->{Date} cmp $matches->{$b}->{Date} + || $a cmp $b; +} + +# menu_trackname(entry) +# +# Format the track name for display in an FVWM menu, where entry +# is a hash reference containing the track metadata. +sub menu_trackname { + my ($entry) = @_; + my $fmt = "$entry->{trackfmt}$entry->{Artist} - $entry->{Title}"; + return "$entry->{thumb}" . fvwm_label_escape($fmt); +} sub print_version { print < \$port, 'menu|m=s' => \$menu, - 'album=s' => sub { $album = $_[1]; $mode = "album"; }, - 'artist=s' => sub { $artist = $_[1]; - $mode = "artist" unless $mode eq "album"; }, + 'album-id=s' => sub { $albumid = $_[1]; $mode = "album"; }, + 'artist=s' => sub { $artist = $_[1]; $mode = "artist"; }, 'title=s' => sub { $title = $_[1]; $mode = "track"; }, 'V|version' => sub { print_version(); exit }, @@ -412,106 +486,89 @@ if ($mode eq "top") { "Exec", "exec", "$FindBin::Bin/mpdexec.pl", "play"); } } elsif ($mode eq "album") { - # Create an album menu. - my @playlist = (); - my $entry; - - $menu = "MenuMPDAlbum" unless defined $menu; - - $album =~ s/"/\\"/g; - print $sock "playlistfind album \"$album\"\n"; - while (<$sock>) { - last if (/^OK/); - die($_) if (/^ACK/); - - if (/^(\w+): (.*)$/) { - if ($1 eq "file") { - if (keys(%$entry) > 0) { - addalbumentry(\@playlist, $entry) - } - - $entry = {}; + my $matches = get_tracks_by_release_mbid($albumid); + my @notqueued = (); + + $menu //= "MenuMPDAlbum"; + + my $track_max = max(map { $_->{Track} } values %$matches); + my $disc_max = max(map { $_->{Disc} } values %$matches); + + # CDs have a max of 99 tracks and I hope 100+-disc-releases + # don't exist so this is fine. + my $track_digits = $track_max >= 10 ? 2 : 1; + my $disc_digits = $disc_max > 1 ? $disc_max >= 10 ? 2 : 1 : 0; + my $currentdisc; + + fvwm_cmd("AddToMenu", $menu); + fvwm_cmd("+", "Release not found", "Title") unless keys %$matches; + foreach my $file (sort { albumsort($matches, $a, $b) } keys %$matches) { + my $entry = $matches->{$file}; + + # Format disc/track numbers + $entry->{trackfmt} = sprintf("%*.*s%.*s%*d\t", + $disc_digits, $disc_digits, $entry->{Disc}, + $disc_digits, "-", + $track_digits, $entry->{Track}); + $entry->{trackfmt} =~ s/ /\N{U+2007}/g; + + unless (exists $entry->{Id}) { + my ($id) = get_ids_by_filename($file); + if (defined $id) { + $entry->{Id} = $id; + } else { + push @notqueued, $entry; + next; } - - $entry->{$1} = $2; } - } - addalbumentry(\@playlist, $entry) if (keys(%$entry) > 0); - die("No tracks found.\n") if (!@playlist); - foreach (sort albumsort @playlist) { - my ($t_file, $t_trackno, $t_artist, $t_title, $t_id) = ( - $_->{file}, - $_->{Track}, - $_->{Artist}, - $_->{Title}, - $_->{Id}, - ); - - next if (defined $artist && !$accept{albumdir($t_file)}); - - $t_artist = sanitise($t_artist, 0); - $t_title = sanitise($t_title, 0); + if (defined $currentdisc && $currentdisc != $entry->{Disc}) { + fvwm_cmd("+", "", "Nop"); + } + $currentdisc = $entry->{Disc}; - my $cmd = sprintf "AddToMenu $menu \"%d\t%s - %s\"" - ." Exec exec $FindBin::Bin/mpdexec.pl" - ." playid %d", - $t_trackno, $t_artist, $t_title, $t_id; + fvwm_cmd("+", menu_trackname($entry), "Exec", + "exec", "$FindBin::Bin/mpdexec.pl", + "playid", $entry->{Id}); + } - cmd($cmd); + fvwm_cmd("+", "Not in play queue", "Title") if @notqueued; + foreach my $entry (@notqueued) { + fvwm_cmd("+", menu_trackname($entry)); } } elsif ($mode eq "artist") { # Create an artist menu. - my %albums = (); - my $file; - my $quoteartist = $artist; + my %matches; + my $entry; - $menu = "MenuMPDArtist" unless defined $menu; + $menu //= "MenuMPDArtist"; - $quoteartist =~ s/"/\\"/g; - print $sock "playlistfind artist \"$quoteartist\"\n"; + mpd_exec("playlistfind", "artist", $artist); while (<$sock>) { last if (/^OK/); die($_) if (/^ACK/); if (/^(\w+): (.*)$/) { - $file = $2 if ($1 eq "file"); - $albums{$2} = $file if ($1 eq "Album"); + $entry = {} if ($1 eq "file"); + $matches{$2} //= $entry if ($1 eq "MUSICBRAINZ_ALBUMID"); + add_track_metadata($entry, $1, $2); } } - die("No albums found.\n") if (!keys(%albums)); - -{ # work around 'use locale' breaking s///i - my $i = 0; - use locale; - - my @keys = sort keys %albums; - my @thumbs = get_item_thumbnails({ small => 1 }, - map { $albums{$_} } @keys); + my @mbids = sort { datesort(\%matches, $a, $b) } keys %matches; + my @files = map { $matches{$_}->{file} } @mbids; + my @thumbs = get_item_thumbnails({ small => 1 }, @files); + fvwm_cmd("AddToMenu", $menu, "No releases found", "Title") unless @mbids; - foreach my $key (@keys) { - my $a_album = sanitise($key, 1); + foreach my $mbid (@mbids) { + my $entry = $matches{$mbid}; my $thumb = shift @thumbs; - cmd("AddToMenu $menu \"$thumb$a_album\" Popup MenuMPDArt_$i"); - - cmd("AddToMenu MenuMPDArt_$i DynamicPopUpAction MakeMenuMPDArt_$i"); - - cmd("DestroyFunc MakeMenuMPDArt_$i"); - cmd("AddToFunc MakeMenuMPDArt_$i - + I DestroyMenu MenuMPDArt_$i - + I -PipeRead \"exec $SELF " - ."--menu MenuMPDArt_$i " - ."--album ".shellify($key, 1)." " - ."--artist ".shellify($artist, 1)."\""); - - cmd("AddToFunc KillMenuMPD I DestroyMenu MenuMPDArt_$i"); - cmd("AddToFunc KillMenuMPD I DestroyFunc MakeMenuMPDArt_$i"); - - $i++; + my @submenu = make_submenu("$menu-$mbid", "--album-id=$mbid"); + fvwm_cmd("AddToMenu", $menu, + $thumb . fvwm_label_escape($entry->{Album}), + @submenu); } -} # end use locale workaround } elsif ($mode eq "track") { # Create a title menu. my @titles; @@ -613,32 +670,6 @@ sub sanitise return $_; } -sub addalbumentry -{ - my ($playlist, $entry) = @_; - - push(@$playlist, $entry); - - if (defined $artist && $artist eq $entry->{Artist}) { - my $albumdir = albumdir($entry->{file}); - $accept{$albumdir} = 1; - } -} - -sub albumdir -{ - my $file = $_[0]; - - $file =~ s:(/Disk [0-9]+[^/]*)?/[^/]*$::; - return $file -} - -sub albumsort -{ - return ($a->{Disc} <=> $b->{Disc}) if ($a->{Disc} != $b->{Disc}); - return ($a->{Track} <=> $b->{Track}); -} - sub titlesort { return ($a->{Album} cmp $b->{Album}) if($a->{Album} ne $b->{Album}); @@ -646,17 +677,6 @@ sub titlesort return ($a->{Title} cmp $b->{Title}); } -sub shellify -{ - my ($str, $quoted) = @_; - $str =~ s/'/'\\''/g; - if ($quoted) { - $str =~ s/\\/\\\\/g; - $str =~ s/"/\\"/g; - } - return "'$str'"; -} - sub cmd { print "$_[0]\n";